@vobs/compiler 1.0.0 → 1.2.0
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/README.md +2 -0
- package/dist/compile.cjs +1149 -0
- package/dist/compile.cjs.map +1 -0
- package/dist/compile.d.cts +8 -0
- package/dist/compile.d.ts +8 -0
- package/dist/compile.js +1141 -0
- package/dist/compile.js.map +1 -0
- package/dist/i18n-extractor.cjs +51 -0
- package/dist/i18n-extractor.cjs.map +1 -0
- package/dist/i18n-extractor.d.cts +16 -0
- package/dist/i18n-extractor.d.ts +16 -0
- package/dist/i18n-extractor.js +45 -0
- package/dist/i18n-extractor.js.map +1 -0
- package/dist/index.cjs +1187 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1178 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.cjs +4 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +70 -0
- package/dist/plugin.d.ts +70 -0
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +26 -6
- package/src/compile.test.ts +98 -16
- package/src/compile.ts +409 -170
- package/src/plugin.ts +5 -0
package/src/compile.test.ts
CHANGED
|
@@ -24,10 +24,10 @@ describe('compiler', () => {
|
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
it('编译 JSX 为 DOM 渲染调用', () => {
|
|
27
|
-
const code = `const el = <div>hello</div>`
|
|
28
|
-
|
|
27
|
+
const code = `const el = <div>hello {name.value}</div>`
|
|
28
|
+
|
|
29
29
|
const result = compile(code)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
expect(result).toContain('createElement')
|
|
32
32
|
expect(result).toContain('createText')
|
|
33
33
|
expect(result).toContain('insertBefore')
|
|
@@ -37,12 +37,12 @@ describe('compiler', () => {
|
|
|
37
37
|
const code = `
|
|
38
38
|
function Counter() {
|
|
39
39
|
const count = state(0)
|
|
40
|
-
return <div>hello</div>
|
|
40
|
+
return <div>hello {count.value}</div>
|
|
41
41
|
}
|
|
42
42
|
`
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
const result = compile(code)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
expect(result).toContain('function Counter')
|
|
47
47
|
expect(result).toContain('createElement')
|
|
48
48
|
})
|
|
@@ -51,13 +51,50 @@ describe('compiler', () => {
|
|
|
51
51
|
const code = `
|
|
52
52
|
const el = <button onClick={() => { console.log('clicked') }}>点击</button>
|
|
53
53
|
`
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
const result = compile(code)
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
expect(result).toContain('addEventListener')
|
|
58
58
|
expect(result).toContain('click')
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
+
it('重入安全:插件在编译过程中调用 compile() 不污染外层编译状态', () => {
|
|
62
|
+
// 外层源码的变量名会命中内层片段用到的 helper 名,验证 takenNames/helperAliases
|
|
63
|
+
// 等状态在外层编译全程保持独立(此前为模块级变量,嵌套编译会互相覆盖)。
|
|
64
|
+
const nestedPlugin: CompilerPlugin = {
|
|
65
|
+
name: 'nested-compile',
|
|
66
|
+
analyze() {
|
|
67
|
+
compile(`const el = <span>inner</span>`, { filename: 'inner.tsx' })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const code = `
|
|
71
|
+
const createElement = () => null
|
|
72
|
+
const el = <div>outer {name.value}</div>
|
|
73
|
+
`
|
|
74
|
+
|
|
75
|
+
const result = compileWithSourceMap(code, {
|
|
76
|
+
filename: 'outer.tsx',
|
|
77
|
+
plugins: [nestedPlugin]
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// 外层的局部绑定 createElement 仍在,运行时 helper 注入为别名导入
|
|
81
|
+
expect(result.code).toContain('const createElement = () => null')
|
|
82
|
+
expect(result.code).toContain('_vobs_createElement')
|
|
83
|
+
expect(result.code).toContain('outer')
|
|
84
|
+
expect(result.diagnostics).toEqual([])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('重入安全:连续多次编译各自独立,计数器与别名不跨编译泄漏', () => {
|
|
88
|
+
const first = compileWithSourceMap(`const a = <div className="x">{a.value}</div>`, { filename: 'a.tsx' })
|
|
89
|
+
const second = compileWithSourceMap(`const b = <div className="y">{b.value}</div>`, { filename: 'b.tsx' })
|
|
90
|
+
|
|
91
|
+
// 临时变量计数器每次编译从 0 开始
|
|
92
|
+
expect(first.code).toContain('_el0')
|
|
93
|
+
expect(second.code).toContain('_el0')
|
|
94
|
+
// 第二次编译不带第一次残留的诊断
|
|
95
|
+
expect(second.diagnostics).toEqual([])
|
|
96
|
+
})
|
|
97
|
+
|
|
61
98
|
it('将静态属性合并为一次 setStaticProps 调用', () => {
|
|
62
99
|
const result = compile(`const el = <input className="field" disabled id="name" />`)
|
|
63
100
|
expect(result).toContain('setStaticProps')
|
|
@@ -85,6 +122,38 @@ describe('compiler', () => {
|
|
|
85
122
|
expect(result).toContain('state(0)')
|
|
86
123
|
})
|
|
87
124
|
|
|
125
|
+
it('自动推断 state() 的 debugName(未显式传参时使用变量名)', () => {
|
|
126
|
+
const result = compile(`import { state } from '@vobs/reactivity'\nconst username = state('')`)
|
|
127
|
+
|
|
128
|
+
expect(result).toContain(`state('', "username")`)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('别名导入的 state 同样推断 debugName', () => {
|
|
132
|
+
const result = compile(`import { state as signal } from '@vobs/reactivity'\nconst username = signal('')`)
|
|
133
|
+
|
|
134
|
+
expect(result).toContain(`signal('', "username")`)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('显式传入 debugName 时不覆盖', () => {
|
|
138
|
+
const result = compile(`import { state } from '@vobs/reactivity'\nconst username = state('', 'auth.name')`)
|
|
139
|
+
|
|
140
|
+
expect(result).toContain(`state('', 'auth.name')`)
|
|
141
|
+
expect(result).not.toContain('"username"')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('state 被本地声明遮蔽时不推断 debugName', () => {
|
|
145
|
+
const result = compile(`import { state } from '@vobs/reactivity'\nfunction state(value: unknown) { return value }\nconst username = state('')`)
|
|
146
|
+
|
|
147
|
+
expect(result).toContain(`state('')`)
|
|
148
|
+
expect(result).not.toContain('"username"')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('非 state 调用不推断 debugName', () => {
|
|
152
|
+
const result = compile(`const items = useState([])`)
|
|
153
|
+
|
|
154
|
+
expect(result).toContain('useState([])')
|
|
155
|
+
})
|
|
156
|
+
|
|
88
157
|
it('编译条件 JSX 为动态块', () => {
|
|
89
158
|
const result = compile(`const el = <div>{show.value && <span>visible</span>}</div>`)
|
|
90
159
|
|
|
@@ -168,9 +237,11 @@ describe('compiler', () => {
|
|
|
168
237
|
it('只导入生成代码实际使用的运行时 helper', () => {
|
|
169
238
|
const result = compile(`const el = <div>hello</div>`)
|
|
170
239
|
|
|
171
|
-
|
|
172
|
-
expect(result).toContain('
|
|
173
|
-
expect(result).toContain('
|
|
240
|
+
// 完全静态的子树提升为模板:只需 createTemplate + cloneTemplate
|
|
241
|
+
expect(result).toContain('createTemplate')
|
|
242
|
+
expect(result).toContain('cloneTemplate')
|
|
243
|
+
expect(result).not.toContain('createElement')
|
|
244
|
+
expect(result).not.toContain('insertBefore')
|
|
174
245
|
expect(result).not.toContain('bindAttribute')
|
|
175
246
|
expect(result).not.toContain('bindText')
|
|
176
247
|
expect(result).not.toContain('insertDynamic')
|
|
@@ -240,7 +311,7 @@ describe('compiler', () => {
|
|
|
240
311
|
const result = compile(`
|
|
241
312
|
import { createElement } from './shim'
|
|
242
313
|
export const tag = createElement('span')
|
|
243
|
-
const el = <div>hello</div>
|
|
314
|
+
const el = <div>hello {name.value}</div>
|
|
244
315
|
`)
|
|
245
316
|
|
|
246
317
|
// 用户导入与调用保持不变,编译器 helper 走别名,不再产生重复声明
|
|
@@ -255,12 +326,12 @@ describe('compiler', () => {
|
|
|
255
326
|
it('源文件本地声明与 helper 同名时注入别名 import', () => {
|
|
256
327
|
const result = compile(`
|
|
257
328
|
const createText = (value: string) => value
|
|
258
|
-
const el = <div>hello</div>
|
|
329
|
+
const el = <div>hello {name.value}</div>
|
|
259
330
|
`)
|
|
260
331
|
|
|
261
332
|
expect(result).toContain('createText as _vobs_createText')
|
|
262
333
|
expect(result).toContain('from "@vobs/vobs"')
|
|
263
|
-
expect(result).toContain('insertBefore(_el0, _vobs_createText("hello")')
|
|
334
|
+
expect(result).toContain('insertBefore(_el0, _vobs_createText("hello ")')
|
|
264
335
|
// 用户本地声明不受影响
|
|
265
336
|
expect(result).toContain('const createText = (value: string) => value')
|
|
266
337
|
})
|
|
@@ -283,7 +354,7 @@ describe('compiler', () => {
|
|
|
283
354
|
it('同名绑定触发别名时保留别名一致性(同一 helper 只注入一次)', () => {
|
|
284
355
|
const result = compile(`
|
|
285
356
|
const createElement = String
|
|
286
|
-
const el = <div><span>one</span></div>
|
|
357
|
+
const el = <div><span>one {name.value}</span></div>
|
|
287
358
|
`)
|
|
288
359
|
|
|
289
360
|
expect(result.match(/_vobs_createElement/g)?.length).toBeGreaterThanOrEqual(2)
|
|
@@ -294,7 +365,7 @@ describe('compiler', () => {
|
|
|
294
365
|
it('生成的临时变量避开用户已声明的名称', () => {
|
|
295
366
|
const result = compile(`
|
|
296
367
|
const _el0 = 'reserved'
|
|
297
|
-
const el = <div>hello</div>
|
|
368
|
+
const el = <div>hello {name.value}</div>
|
|
298
369
|
`)
|
|
299
370
|
|
|
300
371
|
// _el0 被用户占用,编译产物必须改用下一个可用名称
|
|
@@ -335,6 +406,17 @@ describe('compiler', () => {
|
|
|
335
406
|
expect(result).toContain('line: 2')
|
|
336
407
|
})
|
|
337
408
|
|
|
409
|
+
it('sourceLocation: false 剔除组件源码位置', () => {
|
|
410
|
+
const result = compile(`
|
|
411
|
+
const el = <Editor><input disabled={locked.value} value={text.value} /></Editor>
|
|
412
|
+
`, { filename: 'src/editor.tsx', sourceLocation: false })
|
|
413
|
+
|
|
414
|
+
expect(result).toContain('createComponent')
|
|
415
|
+
expect(result).not.toContain('file:')
|
|
416
|
+
expect(result).not.toContain('line:')
|
|
417
|
+
expect(result).not.toContain('column:')
|
|
418
|
+
})
|
|
419
|
+
|
|
338
420
|
it('生成包含原始内容的 Source Map', () => {
|
|
339
421
|
const result = compileWithSourceMap(`const el = <div>hello</div>`, { filename: 'src/App.tsx' })
|
|
340
422
|
|