actview 1.0.4 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,3 +1 @@
1
- export * from '@actview/core';
2
- export * from '@actview/router';
3
- //# sourceMappingURL=index.d.ts.map
1
+ export { App, createApp, defineComponent, markRaw, reactive, readonly, shallowReactive } from '@actview/core';
package/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // src/index.ts
2
+ import {
3
+ createApp,
4
+ defineComponent,
5
+ reactive,
6
+ shallowReactive,
7
+ readonly,
8
+ markRaw
9
+ } from "@actview/core";
10
+ export {
11
+ createApp,
12
+ defineComponent,
13
+ markRaw,
14
+ reactive,
15
+ readonly,
16
+ shallowReactive
17
+ };
package/package.json CHANGED
@@ -1,46 +1,22 @@
1
1
  {
2
2
  "name": "actview",
3
- "version": "1.0.4",
4
- "description": "Configuration-driven MVVM born for Campaign/Marketing single pages: selector + config for automatic data binding, supporting reactive/ref, computed, watch/watchEffect, JSX rendering, and quick event mounting.",
3
+ "version": "1.0.7",
5
4
  "type": "module",
6
- "main": "./index.cjs",
7
- "module": "./index.mjs",
8
- "types": "./index.d.ts",
9
5
  "exports": {
10
6
  ".": {
11
- "import": {
12
- "types": "./index.d.ts",
13
- "default": "./index.mjs"
14
- },
15
- "require": {
16
- "types": "./index.d.ts",
17
- "default": "./index.cjs"
18
- }
7
+ "types": "./index.d.ts",
8
+ "import": "./index.js"
19
9
  }
20
10
  },
21
- "keywords": [
22
- "actview",
23
- "reactive",
24
- "ref",
25
- "computed",
26
- "watch"
27
- ],
28
- "author": "scliuyilin",
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": ""
33
- },
34
- "homepage": "",
35
- "bugs": {
36
- "url": ""
37
- },
38
11
  "dependencies": {
39
- "@actview/core": "^1.0.4",
40
- "@actview/jsx": "^1.0.4",
41
- "@actview/router": "^1.0.4"
12
+ "@actview/core": "^1.0.7",
13
+ "@actview/jsx": "^1.0.9"
14
+ },
15
+ "scripts": {
16
+ "build": "tsup && node ../../scripts/rewrite-package.mjs .",
17
+ "release": "pnpm build && cd dist && npm publish"
42
18
  },
43
- "publishConfig": {
44
- "access": "public"
45
- }
46
- }
19
+ "main": "./index.js",
20
+ "module": "./index.js",
21
+ "types": "./index.d.ts"
22
+ }
package/README.md DELETED
@@ -1,338 +0,0 @@
1
- # Actview
2
-
3
- A lightweight frontend framework with **Vue-like reactivity** and **React-like JSX**, built from scratch with Proxy and a custom JSX factory.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install
9
- npm run dev
10
- ```
11
-
12
- Build:
13
-
14
- ```bash
15
- npm run build
16
- ```
17
-
18
- ## Reactivity System
19
-
20
- ### ref — Primitive Values
21
-
22
- ```tsx
23
- import { ref, computed, watch } from "@actview/core"
24
-
25
- const count = ref(0)
26
-
27
- // Read: count.value
28
- // Write: count.value = 1
29
- console.log(count.value) // 0
30
- count.value++
31
- console.log(count.value) // 1
32
- ```
33
-
34
- ### reactive — Objects & Arrays
35
-
36
- ```tsx
37
- const user = reactive({ name: "Actview", age: 3 })
38
- const list = reactive([{ id: 1, text: "Item A" }])
39
-
40
- console.log(user.name) // "Actview"
41
- user.age = 4 // triggers update
42
-
43
- list.push({ id: 2, text: "Item B" }) // triggers update
44
- ```
45
-
46
- ### computed — Derived Values
47
-
48
- ```tsx
49
- const name = ref("Actview")
50
- const version = ref(1)
51
-
52
- const info = computed(() => `${name.value} v${version.value}`)
53
-
54
- console.log(info.value) // "Actview v1"
55
- ```
56
-
57
- ### watch — Side Effects
58
-
59
- ```tsx
60
- const count = ref(0)
61
-
62
- watch(count, (newVal, oldVal) => {
63
- console.log(`count: ${oldVal} → ${newVal}`)
64
- })
65
-
66
- count.value = 1 // logs "count: 0 → 1"
67
- ```
68
-
69
- ### How It Works
70
-
71
- Actview uses **Proxy** to intercept get/set on reactive data. When a ref is read inside a component's render function, the component automatically subscribes to that ref. When the ref changes, only the components that read it are re-rendered.
72
-
73
- ```tsx
74
- const count = ref(0)
75
-
76
- function Counter() {
77
- return () => <div>{count.value}</div> // subscribes to `count`
78
- }
79
- ```
80
-
81
- ## Components
82
-
83
- ### Setup Mode (Recommended)
84
-
85
- The component function runs once (setup), returns a render function that re-runs on each update:
86
-
87
- ```tsx
88
- function Counter() {
89
- const count = ref(0)
90
-
91
- return () => (
92
- <div>
93
- <p>Count: {count.value}</p>
94
- <button onClick={() => count.value++}>+1</button>
95
- </div>
96
- )
97
- }
98
- ```
99
-
100
- - **Setup** runs once: create refs, computed, watch
101
- - **Render** runs on each update: returns JSX, auto-tracks reactive dependencies
102
-
103
- ### Direct Mode (Stateless)
104
-
105
- The component function IS the render function — re-runs on every parent render:
106
-
107
- ```tsx
108
- function Greeting(props: { name: string }) {
109
- return <div>Hello, {props.name}!</div>
110
- }
111
- ```
112
-
113
- No internal state, no isolated reactive boundary. Pure props-in / JSX-out.
114
-
115
- ### Props
116
-
117
- ```tsx
118
- function Welcome(props: { title: string; count: number }) {
119
- return () => (
120
- <div>
121
- <h2>{props.title}</h2>
122
- <p>Count: {props.count}</p>
123
- </div>
124
- )
125
- }
126
-
127
- // Usage
128
- <Welcome title="Hello" count={count.value} />
129
- ```
130
-
131
- ## JSX & DOM Rendering
132
-
133
- Actview uses a **custom JSX factory** (`createElement`) that creates real DOM nodes directly — no virtual DOM.
134
-
135
- ```tsx
136
- // JSX → createElement → real DOM
137
- const element = <div class="card">
138
- <h3>Title</h3>
139
- <p>Content</p>
140
- </div>
141
- ```
142
-
143
- ### Reactive Updates (Component-Level)
144
-
145
- ```tsx
146
- function Timer() {
147
- const time = ref(new Date().toLocaleTimeString())
148
-
149
- setInterval(() => {
150
- time.value = new Date().toLocaleTimeString()
151
- }, 1000)
152
-
153
- return () => <div>Current time: {time.value}</div>
154
- }
155
- ```
156
-
157
- - `time.value` changes → triggers `publish`
158
- - Only this component's `componentUpdateFn` re-runs
159
- - Compares old and new DOM nodes, patches only the changed parts
160
-
161
- ### Fragment
162
-
163
- ```tsx
164
- function List() {
165
- return () => (
166
- <>
167
- <h2>Items</h2>
168
- {items.map(item => <div key={item.id}>{item.text}</div>)}
169
- </>
170
- )
171
- }
172
- ```
173
-
174
- Fragment wraps children in a `DocumentFragment` with start/end comment anchors for stable diffing.
175
-
176
- ### Keyed Reconciliation
177
-
178
- Child nodes with a `key` prop are matched by identity, preserving DOM state (e.g., input focus):
179
-
180
- ```tsx
181
- function SortableList() {
182
- const items = reactive([
183
- { id: 1, text: "A" },
184
- { id: 2, text: "B" },
185
- ])
186
-
187
- return () => (
188
- <>
189
- {items.map(item => (
190
- <div key={item.id}>
191
- <input placeholder={item.text} />
192
- </div>
193
- ))}
194
- <button onClick={() => items.sort(() => Math.random() - 0.5)}>
195
- Shuffle
196
- </button>
197
- </>
198
- )
199
- }
200
- ```
201
-
202
- Without keys, shuffling would misplace input values. With keys, each input stays with its matching `div`.
203
-
204
- ## Slots
205
-
206
- Named slots use `<template slot="xxx">` syntax. The template's content is extracted and passed as `props.xxx` to the component. Content not wrapped in `<template slot>` becomes `props.children` (default slot).
207
-
208
- ### Defining a Component with Slots
209
-
210
- ```tsx
211
- function Card() {
212
- return (props: any) => (
213
- <div class="card">
214
- {props.header && <div class="card-header">{props.header}</div>}
215
- <div class="card-body">{props.children}</div>
216
- {props.footer && <div class="card-footer">{props.footer}</div>}
217
- </div>
218
- )
219
- }
220
- ```
221
-
222
- ### Using Slots
223
-
224
- ```tsx
225
- <Card>
226
- <template slot="header">
227
- <span>📌</span>
228
- <strong>Title</strong>
229
- </template>
230
-
231
- <p>Main body content — becomes props.children.</p>
232
-
233
- <template slot="footer">
234
- <span>Footer info</span>
235
- </template>
236
- </Card>
237
- ```
238
-
239
- How it works inside `createElement`:
240
-
241
- 1. Children passed to the component are iterated
242
- 2. Elements matching `child instanceof HTMLTemplateElement && child.getAttribute("slot")` have their content extracted into the named prop
243
- 3. Other children are collected into `props.children`
244
-
245
- ```ts
246
- // Pseudocode of the slot resolution
247
- for (const child of allChildren) {
248
- if (child instanceof HTMLTemplateElement && child.getAttribute("slot")) {
249
- const slotName = child.getAttribute("slot")!
250
- mergedProps[slotName] = Array.from(child.content.childNodes)
251
- } else {
252
- defaultChildren.push(child)
253
- }
254
- }
255
- mergedProps.children = defaultChildren
256
- ```
257
-
258
- ## Router
259
-
260
- Actview includes a client-side router with nested route support.
261
-
262
- ### Setup
263
-
264
- ```tsx
265
- import { Router } from "@actview/router"
266
- import { Home } from "./pages/home"
267
- import { About } from "./pages/about"
268
-
269
- const routes = [
270
- { path: "/", component: Home },
271
- { path: "/about", component: About },
272
- { path: "/admin", component: AdminLayout, children: [
273
- { path: "/admin/users", component: Users },
274
- { path: "/admin/settings", component: Settings },
275
- ]},
276
- ]
277
-
278
- new Router({ routes })
279
- ```
280
-
281
- ### Navigation
282
-
283
- ```tsx
284
- import { useRouter } from "@actview/router"
285
-
286
- function NavBar() {
287
- const router = useRouter()
288
-
289
- return () => (
290
- <nav>
291
- <a onClick={() => router.push("/")}>Home</a>
292
- <a onClick={() => router.push("/about")}>About</a>
293
- </nav>
294
- )
295
- }
296
- ```
297
-
298
- ### RouterView
299
-
300
- ```tsx
301
- import { RouterView } from "@actview/router"
302
-
303
- function App() {
304
- return () => (
305
- <div>
306
- <NavBar />
307
- <RouterView /> {/* renders matched route component */}
308
- </div>
309
- )
310
- }
311
- ```
312
-
313
- For nested routes, place `<RouterView />` inside the parent layout component — it automatically renders the child route at the current depth.
314
-
315
- ## Architecture
316
-
317
- ```
318
- packages/
319
- core/ Reactivity (ref, reactive, computed, watch) + EventBus
320
- jsx/ Custom JSX factory (createElement, mountComponent, diffElement)
321
- router/ Client-side router with nested route support
322
- actview/ Package re-exports
323
- ```
324
-
325
- ### Diff Algorithm
326
-
327
- `diffElement` compares two real DOM nodes directly (no virtual DOM):
328
-
329
- 1. **Type mismatch** → replace
330
- 2. **Text node** → update `textContent`
331
- 3. **Component boundary** → skip (component manages its own subtree)
332
- 4. **Same element** → sync attributes → reconcile children (keyed or index-based)
333
-
334
- Component boundary isolation means a re-rendering parent does not diff into child components. Each component has its own `componentUpdateFn` that runs independently when its reactive dependencies change.
335
-
336
- ## License
337
-
338
- MIT
package/README.zh.md DELETED
@@ -1,339 +0,0 @@
1
- # Actview
2
-
3
- 轻量级前端框架,融合 **Vue 风格的响应式系统** 与 **React 风格的 JSX**,基于 Proxy 和自定义 JSX 工厂从零实现。
4
-
5
- ## 安装
6
-
7
- ```bash
8
- npm install
9
- npm run dev
10
- ```
11
-
12
- 构建:
13
-
14
- ```bash
15
- npm run build
16
- ```
17
-
18
- ## 响应式系统
19
-
20
- ### ref — 基础类型
21
-
22
- ```tsx
23
- import { ref, computed, watch } from "@actview/core"
24
-
25
- const count = ref(0)
26
-
27
- // 读取: count.value
28
- // 写入: count.value = 1
29
- console.log(count.value) // 0
30
- count.value++
31
- console.log(count.value) // 1
32
- ```
33
-
34
- ### reactive — 对象与数组
35
-
36
- ```tsx
37
- const user = reactive({ name: "Actview", age: 3 })
38
- const list = reactive([{ id: 1, text: "条目 A" }])
39
-
40
- console.log(user.name) // "Actview"
41
- user.age = 4 // 触发更新
42
-
43
- list.push({ id: 2, text: "条目 B" }) // 触发更新
44
- ```
45
-
46
- ### computed — 计算属性
47
-
48
- ```tsx
49
- const name = ref("Actview")
50
- const version = ref(1)
51
-
52
- const info = computed(() => `${name.value} v${version.value}`)
53
-
54
- console.log(info.value) // "Actview v1"
55
- ```
56
-
57
- ### watch — 侦听器
58
-
59
- ```tsx
60
- const count = ref(0)
61
-
62
- watch(count, (newVal, oldVal) => {
63
- console.log(`count: ${oldVal} → ${newVal}`)
64
- })
65
-
66
- count.value = 1 // 输出 "count: 0 → 1"
67
- ```
68
-
69
- ### 原理
70
-
71
- Actview 使用 **Proxy** 拦截响应式数据的读写。组件渲染函数中读取的 ref 会自动建立订阅关系。数据变更时,仅依赖该数据的组件重新渲染。
72
-
73
- ```tsx
74
- const count = ref(0)
75
-
76
- function Counter() {
77
- return () => <div>{count.value}</div> // 订阅 count
78
- }
79
- ```
80
-
81
- ## 组件
82
-
83
- ### Setup 模式(推荐)
84
-
85
- 组件函数执行一次(setup),返回渲染函数(render),每次更新时重新执行渲染函数:
86
-
87
- ```tsx
88
- function Counter() {
89
- const count = ref(0)
90
-
91
- return () => (
92
- <div>
93
- <p>Count: {count.value}</p>
94
- <button onClick={() => count.value++}>+1</button>
95
- </div>
96
- )
97
- }
98
- ```
99
-
100
- - **Setup 阶段**:创建 ref、computed、watch 等,只执行一次
101
- - **Render 阶段**:返回 JSX,自动追踪响应式依赖,每次更新重新执行
102
-
103
- ### 直接模式(无状态)
104
-
105
- 组件函数本身就是渲染函数,父组件每次渲染都会重新执行:
106
-
107
- ```tsx
108
- function Greeting(props: { name: string }) {
109
- return <div>你好,{props.name}!</div>
110
- }
111
- ```
112
-
113
- 无内部状态,无独立响应式边界,纯 props 输入 / JSX 输出。
114
-
115
- ### Props
116
-
117
- ```tsx
118
- function Welcome(props: { title: string; count: number }) {
119
- return () => (
120
- <div>
121
- <h2>{props.title}</h2>
122
- <p>Count: {props.count}</p>
123
- </div>
124
- )
125
- }
126
-
127
- // 使用
128
- <Welcome title="Hello" count={count.value} />
129
- ```
130
-
131
- ## JSX & DOM 渲染
132
-
133
- Actview 使用 **自定义 JSX 工厂**(`createElement`)直接创建真实 DOM,无虚拟 DOM:
134
-
135
- ```tsx
136
- // JSX → createElement → 真实 DOM
137
- const element = <div class="card">
138
- <h3>标题</h3>
139
- <p>内容</p>
140
- </div>
141
- ```
142
-
143
- ### 响应式更新(组件级)
144
-
145
- ```tsx
146
- function Timer() {
147
- const time = ref(new Date().toLocaleTimeString())
148
-
149
- setInterval(() => {
150
- time.value = new Date().toLocaleTimeString()
151
- }, 1000)
152
-
153
- return () => <div>当前时间: {time.value}</div>
154
- }
155
- ```
156
-
157
- - `time.value` 变化 → 触发 `publish`
158
- - 仅该组件的 `componentUpdateFn` 重新执行
159
- - 对比新旧 DOM,仅更新变化的节点
160
-
161
- ### Fragment
162
-
163
- ```tsx
164
- function List() {
165
- return () => (
166
- <>
167
- <h2>列表</h2>
168
- {items.map(item => <div key={item.id}>{item.text}</div>)}
169
- </>
170
- )
171
- }
172
- ```
173
-
174
- Fragment 用 `DocumentFragment` 包含子节点,配合首尾锚点注释节点实现稳定 diff。
175
-
176
- ### Keyed Reconciliation
177
-
178
- 子节点带有 `key` 属性时按标识符匹配,保留 DOM 状态(如输入框内容):
179
-
180
- ```tsx
181
- function SortableList() {
182
- const items = reactive([
183
- { id: 1, text: "A" },
184
- { id: 2, text: "B" },
185
- ])
186
-
187
- return () => (
188
- <>
189
- {items.map(item => (
190
- <div key={item.id}>
191
- <input placeholder={item.text} />
192
- </div>
193
- ))}
194
- <button onClick={() => items.sort(() => Math.random() - 0.5)}>
195
- 随机排序
196
- </button>
197
- </>
198
- )
199
- }
200
- ```
201
-
202
- 无 key 时随机排序会导致输入框内容错位,有 key 时每个输入框跟随其 `div`。
203
-
204
- ## 插槽
205
-
206
- 命名插槽使用 `<template slot="xxx">` 语法。模板内的内容会被提取为 `props.xxx` 传入组件。未包裹在 `<template slot>` 中的内容归入 `props.children`(默认插槽)。
207
-
208
- ### 定义带插槽的组件
209
-
210
- ```tsx
211
- function Card() {
212
- return (props: any) => (
213
- <div class="card">
214
- {props.header && <div class="card-header">{props.header}</div>}
215
- <div class="card-body">{props.children}</div>
216
- {props.footer && <div class="card-footer">{props.footer}</div>}
217
- </div>
218
- )
219
- }
220
- ```
221
-
222
- ### 使用插槽
223
-
224
- ```tsx
225
- <Card>
226
- <template slot="header">
227
- <span>📌</span>
228
- <strong>标题</strong>
229
- </template>
230
-
231
- <p>主体内容 — 会成为 props.children。</p>
232
-
233
- <template slot="footer">
234
- <span>底部信息</span>
235
- </template>
236
- </Card>
237
- ```
238
-
239
- ### 原理
240
-
241
- 在 `createElement` 中,传递给组件的子节点会被遍历:
242
-
243
- 1. 命中的 `<template slot="xxx">` 提取其 `content.childNodes` 作为命名 prop
244
- 2. 其余节点归入 `props.children`
245
-
246
- ```ts
247
- // 插槽解析伪代码
248
- for (const child of allChildren) {
249
- if (child instanceof HTMLTemplateElement && child.getAttribute("slot")) {
250
- const slotName = child.getAttribute("slot")!
251
- mergedProps[slotName] = Array.from(child.content.childNodes)
252
- } else {
253
- defaultChildren.push(child)
254
- }
255
- }
256
- mergedProps.children = defaultChildren
257
- ```
258
-
259
- ## 路由
260
-
261
- Actview 内置客户端路由,支持嵌套。
262
-
263
- ### 配置
264
-
265
- ```tsx
266
- import { Router } from "@actview/router"
267
- import { Home } from "./pages/home"
268
- import { About } from "./pages/about"
269
-
270
- const routes = [
271
- { path: "/", component: Home },
272
- { path: "/about", component: About },
273
- { path: "/admin", component: AdminLayout, children: [
274
- { path: "/admin/users", component: Users },
275
- { path: "/admin/settings", component: Settings },
276
- ]},
277
- ]
278
-
279
- new Router({ routes })
280
- ```
281
-
282
- ### 导航
283
-
284
- ```tsx
285
- import { useRouter } from "@actview/router"
286
-
287
- function NavBar() {
288
- const router = useRouter()
289
-
290
- return () => (
291
- <nav>
292
- <a onClick={() => router.push("/")}>首页</a>
293
- <a onClick={() => router.push("/about")}>关于</a>
294
- </nav>
295
- )
296
- }
297
- ```
298
-
299
- ### RouterView
300
-
301
- ```tsx
302
- import { RouterView } from "@actview/router"
303
-
304
- function App() {
305
- return () => (
306
- <div>
307
- <NavBar />
308
- <RouterView /> {/* 渲染匹配当前路由的组件 */}
309
- </div>
310
- )
311
- }
312
- ```
313
-
314
- 嵌套路由时,在父布局组件中放置 `<RouterView />`,自动渲染当前深度的子路由。
315
-
316
- ## 架构
317
-
318
- ```
319
- packages/
320
- core/ 响应式系统 (ref, reactive, computed, watch) + EventBus
321
- jsx/ 自定义 JSX 工厂 (createElement, mountComponent, diffElement)
322
- router/ 客户端路由,支持嵌套
323
- actview/ 统一导出
324
- ```
325
-
326
- ### Diff 算法
327
-
328
- `diffElement` 直接比较两个真实 DOM 节点(无虚拟 DOM):
329
-
330
- 1. **类型不同** → 替换
331
- 2. **文本节点** → 更新 `textContent`
332
- 3. **组件边界** → 跳过(组件自己管理子树)
333
- 4. **相同元素** → 同步属性 → 协调子节点(key 匹配或索引匹配)
334
-
335
- 组件边界隔离意味着父组件重新渲染时不会深入到子组件内部。每个组件拥有独立的 `componentUpdateFn`,仅在自身依赖的响应式数据变化时执行。
336
-
337
- ## License
338
-
339
- MIT
package/index.cjs DELETED
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const core = require("@actview/core");
4
- const router = require("@actview/router");
5
- Object.keys(core).forEach((k) => {
6
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
7
- enumerable: true,
8
- get: () => core[k]
9
- });
10
- });
11
- Object.keys(router).forEach((k) => {
12
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
13
- enumerable: true,
14
- get: () => router[k]
15
- });
16
- });
17
- //# sourceMappingURL=index.cjs.map
package/index.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
package/index.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA"}
package/index.mjs DELETED
@@ -1,3 +0,0 @@
1
- export * from "@actview/core";
2
- export * from "@actview/router";
3
- //# sourceMappingURL=index.mjs.map
package/index.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}