@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.ts
CHANGED
|
@@ -10,16 +10,36 @@ import type {
|
|
|
10
10
|
VobsCompiler
|
|
11
11
|
} from './plugin'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
13
|
+
/**
|
|
14
|
+
* 单次编译的全部可变状态。
|
|
15
|
+
*
|
|
16
|
+
* 此前这些字段是模块级变量,编译器因此不可重入:嵌套调用 compile()
|
|
17
|
+
* (如插件内部再次编译片段)会互相污染状态。现在每次 compile 调用
|
|
18
|
+
* 创建独立的 CompileState 并显式穿参,编译器对并发与嵌套完全安全。
|
|
19
|
+
*/
|
|
20
|
+
interface CompileState {
|
|
21
|
+
/** 临时标识符计数器(_el0、_el1…)。 */
|
|
22
|
+
generatedId: number
|
|
23
|
+
filename: string
|
|
24
|
+
/** 最初解析出的源文件。插件 program 变换后的树节点无法回溯原始位置时,用它兜底取行列。 */
|
|
25
|
+
sourceFile: ts.SourceFile
|
|
26
|
+
/** 生成语句 → 原始源码位置,用于 source map 生成。 */
|
|
27
|
+
statementSources: WeakMap<ts.Statement, SourcePosition>
|
|
28
|
+
/** 源文件中已声明的绑定名(含嵌套作用域):注入运行时 import 与生成临时变量时避开命名冲突。 */
|
|
29
|
+
takenNames: Set<string>
|
|
30
|
+
/** 运行时 helper 的规范名 → 产物中的引用名(无冲突时与规范名相同)。 */
|
|
31
|
+
helperAliases: Map<string, string>
|
|
32
|
+
/** 静态模板声明:HTML → 模块级模板变量,按内容去重,输出在 import 之后。 */
|
|
33
|
+
templates: Map<string, ts.Identifier>
|
|
34
|
+
/** 是否为组件调用生成源码位置(生产构建传 false 剔除,减小产物体积)。 */
|
|
35
|
+
sourceLocation: boolean
|
|
36
|
+
/** 从 @vobs/reactivity / @vobs/vobs 导入的 `state` 别名(含 as 别名),用于 debugName 自动推断。 */
|
|
37
|
+
stateAliases: ReadonlySet<string>
|
|
38
|
+
/** 非 import 的本地声明绑定名:`state` 被本地声明遮蔽时禁用 debugName 推断。 */
|
|
39
|
+
localBindings: ReadonlySet<string>
|
|
40
|
+
/** 编译器自身产出的诊断(如不支持的 JSX 形态),与 TypeScript 解析诊断合并返回。 */
|
|
41
|
+
diagnostics: CompilerDiagnostic[]
|
|
42
|
+
}
|
|
23
43
|
|
|
24
44
|
interface SourcePosition {
|
|
25
45
|
readonly line: number
|
|
@@ -62,10 +82,7 @@ export function compile(code: string, options: CompileOptions = {}): string {
|
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
export function compileWithSourceMap(code: string, options: CompileOptions = {}): CompileResult {
|
|
65
|
-
generatedId = 0
|
|
66
85
|
const filename = options.filename ?? 'component.tsx'
|
|
67
|
-
currentFilename = filename
|
|
68
|
-
statementSources = new WeakMap()
|
|
69
86
|
let sourceFile = ts.createSourceFile(
|
|
70
87
|
filename,
|
|
71
88
|
code,
|
|
@@ -73,10 +90,19 @@ export function compileWithSourceMap(code: string, options: CompileOptions = {})
|
|
|
73
90
|
true,
|
|
74
91
|
ts.ScriptKind.TSX
|
|
75
92
|
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
const state: CompileState = {
|
|
94
|
+
generatedId: 0,
|
|
95
|
+
filename,
|
|
96
|
+
sourceFile,
|
|
97
|
+
statementSources: new WeakMap(),
|
|
98
|
+
takenNames: collectDeclaredNames(sourceFile),
|
|
99
|
+
helperAliases: new Map(),
|
|
100
|
+
templates: new Map(),
|
|
101
|
+
sourceLocation: options.sourceLocation ?? true,
|
|
102
|
+
stateAliases: collectStateAliases(sourceFile),
|
|
103
|
+
localBindings: collectLocallyDeclaredNames(sourceFile),
|
|
104
|
+
diagnostics: []
|
|
105
|
+
}
|
|
80
106
|
const cleanFilename = filename.split(/[?#]/u, 1)[0] || filename
|
|
81
107
|
const diagnostics = ts.transpileModule(code, {
|
|
82
108
|
// Vite appends query strings (for example `?direct`) to module IDs;
|
|
@@ -92,9 +118,9 @@ export function compileWithSourceMap(code: string, options: CompileOptions = {})
|
|
|
92
118
|
filename,
|
|
93
119
|
factory: ts.factory,
|
|
94
120
|
addRuntimeImport(name: string): void {
|
|
95
|
-
resolveHelperName(name)
|
|
121
|
+
resolveHelperName(state, name)
|
|
96
122
|
},
|
|
97
|
-
helperRef
|
|
123
|
+
helperRef: (name: string) => helperRef(state, name)
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
for (const plugin of plugins) plugin.analyze?.(sourceFile, context)
|
|
@@ -104,15 +130,22 @@ export function compileWithSourceMap(code: string, options: CompileOptions = {})
|
|
|
104
130
|
for (const plugin of plugins) sourceFile = transformPluginNodes(sourceFile, plugin, context)
|
|
105
131
|
|
|
106
132
|
const statements = sourceFile.statements.map(statement =>
|
|
107
|
-
ts.isImportDeclaration(statement) ? rebuildImport(statement) : transformStatement(statement)
|
|
133
|
+
ts.isImportDeclaration(statement) ? rebuildImport(state, statement) : transformStatement(state, statement)
|
|
108
134
|
)
|
|
109
|
-
|
|
135
|
+
// 模板声明必须先于 runtime import 生成:声明里的 createTemplate 依赖
|
|
136
|
+
// helperRef 注册别名,import 需要在别名全部就绪后再构建。
|
|
137
|
+
const templateDeclarations = createTemplateDeclarations(state)
|
|
138
|
+
const resultFile = ts.factory.updateSourceFile(sourceFile, [
|
|
139
|
+
...createRuntimeImports(state),
|
|
140
|
+
...templateDeclarations,
|
|
141
|
+
...statements
|
|
142
|
+
])
|
|
110
143
|
|
|
111
144
|
const generated = ts.createPrinter().printFile(resultFile)
|
|
112
145
|
return {
|
|
113
146
|
code: generated,
|
|
114
|
-
map: buildSourceMap(filename, code, generated, resultFile),
|
|
115
|
-
diagnostics: [...diagnostics, ...
|
|
147
|
+
map: buildSourceMap(state, filename, code, generated, resultFile),
|
|
148
|
+
diagnostics: [...diagnostics, ...state.diagnostics]
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
151
|
|
|
@@ -153,17 +186,17 @@ function buildCodeFrame(
|
|
|
153
186
|
* 不支持的 JSX 标签形态(成员表达式 `<Foo.Bar>`、命名空间 `<svg:rect>` 等)。
|
|
154
187
|
* 诊断以 error 级返回,compile() 与 Vite 插件会直接失败,不再静默产出无效 DOM 标签。
|
|
155
188
|
*/
|
|
156
|
-
function reportUnsupportedTag(tagName: ts.JsxTagNameExpression): void {
|
|
157
|
-
const sourceFile = tagName.getSourceFile() ??
|
|
189
|
+
function reportUnsupportedTag(state: CompileState, tagName: ts.JsxTagNameExpression): void {
|
|
190
|
+
const sourceFile = tagName.getSourceFile() ?? state.sourceFile
|
|
158
191
|
if (!sourceFile) return
|
|
159
192
|
const label = tagName.getText()
|
|
160
193
|
const kindNote = tagName.kind === ts.SyntaxKind.JsxNamespacedName ? '(JSX 命名空间标签)' : ''
|
|
161
194
|
const { line, column, codeFrame } = buildCodeFrame(sourceFile, tagName.getStart(sourceFile), tagName.getWidth(sourceFile))
|
|
162
|
-
|
|
195
|
+
state.diagnostics.push({
|
|
163
196
|
code: 'VOBS_C101',
|
|
164
197
|
severity: 'error',
|
|
165
198
|
message: `不支持的 JSX 标签形态:<${label}>${kindNote}。组件必须是大写开头的标识符,DOM 元素必须是小写标签名。`,
|
|
166
|
-
location: { file:
|
|
199
|
+
location: { file: state.filename, line, column },
|
|
167
200
|
codeFrame,
|
|
168
201
|
fix: `把 <${label}> 改为 <Component /> 形式的组件或小写 DOM 标签;Fragment 请使用 <Fragment> 或 <>...</>。`
|
|
169
202
|
})
|
|
@@ -183,6 +216,7 @@ interface MappingSegment {
|
|
|
183
216
|
* statement it was produced from.
|
|
184
217
|
*/
|
|
185
218
|
function buildSourceMap(
|
|
219
|
+
state: CompileState,
|
|
186
220
|
filename: string,
|
|
187
221
|
source: string,
|
|
188
222
|
generated: string,
|
|
@@ -190,7 +224,7 @@ function buildSourceMap(
|
|
|
190
224
|
): import('./plugin').VobsSourceMap {
|
|
191
225
|
const reparsed = ts.createSourceFile(filename, generated, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
192
226
|
const segments: MappingSegment[] = []
|
|
193
|
-
walkPairedTrees(resultFile, reparsed, reparsed, segments)
|
|
227
|
+
walkPairedTrees(state, resultFile, reparsed, reparsed, segments)
|
|
194
228
|
segments.sort((a, b) => a.genLine - b.genLine || a.genCol - b.genCol)
|
|
195
229
|
return {
|
|
196
230
|
version: 3,
|
|
@@ -215,6 +249,7 @@ function statementLists(node: ts.Node): readonly ts.Statement[] | null {
|
|
|
215
249
|
* any divergence (length mismatch) simply abandons that subtree.
|
|
216
250
|
*/
|
|
217
251
|
function walkPairedTrees(
|
|
252
|
+
state: CompileState,
|
|
218
253
|
original: ts.Node,
|
|
219
254
|
generated: ts.Node,
|
|
220
255
|
reparsed: ts.SourceFile,
|
|
@@ -227,8 +262,8 @@ function walkPairedTrees(
|
|
|
227
262
|
for (let index = 0; index < originalStatements.length; index++) {
|
|
228
263
|
const originalStatement = originalStatements[index]
|
|
229
264
|
const generatedStatement = generatedStatements[index]
|
|
230
|
-
recordSegment(originalStatement, generatedStatement, reparsed, segments)
|
|
231
|
-
walkPairedTrees(originalStatement, generatedStatement, reparsed, segments)
|
|
265
|
+
recordSegment(state, originalStatement, generatedStatement, reparsed, segments)
|
|
266
|
+
walkPairedTrees(state, originalStatement, generatedStatement, reparsed, segments)
|
|
232
267
|
}
|
|
233
268
|
return
|
|
234
269
|
}
|
|
@@ -239,17 +274,18 @@ function walkPairedTrees(
|
|
|
239
274
|
ts.forEachChild(generated, node => { generatedChildren.push(node) })
|
|
240
275
|
if (originalChildren.length !== generatedChildren.length) return
|
|
241
276
|
for (let index = 0; index < originalChildren.length; index++) {
|
|
242
|
-
walkPairedTrees(originalChildren[index], generatedChildren[index], reparsed, segments)
|
|
277
|
+
walkPairedTrees(state, originalChildren[index], generatedChildren[index], reparsed, segments)
|
|
243
278
|
}
|
|
244
279
|
}
|
|
245
280
|
|
|
246
281
|
function recordSegment(
|
|
282
|
+
state: CompileState,
|
|
247
283
|
originalStatement: ts.Statement,
|
|
248
284
|
generatedStatement: ts.Statement,
|
|
249
285
|
reparsed: ts.SourceFile,
|
|
250
286
|
segments: MappingSegment[]
|
|
251
287
|
): void {
|
|
252
|
-
const source = statementSources.get(originalStatement) ?? positionOfOriginalStatement(originalStatement)
|
|
288
|
+
const source = state.statementSources.get(originalStatement) ?? positionOfOriginalStatement(state, originalStatement)
|
|
253
289
|
if (!source) return
|
|
254
290
|
const position = reparsed.getLineAndCharacterOfPosition(generatedStatement.getStart(reparsed))
|
|
255
291
|
segments.push({
|
|
@@ -260,10 +296,10 @@ function recordSegment(
|
|
|
260
296
|
})
|
|
261
297
|
}
|
|
262
298
|
|
|
263
|
-
function positionOfOriginalStatement(statement: ts.Statement): SourcePosition | null {
|
|
264
|
-
if (statement.pos < 0 || !
|
|
265
|
-
const { line, character } =
|
|
266
|
-
statement.getStart(
|
|
299
|
+
function positionOfOriginalStatement(state: CompileState, statement: ts.Statement): SourcePosition | null {
|
|
300
|
+
if (statement.pos < 0 || !state.sourceFile) return null
|
|
301
|
+
const { line, character } = state.sourceFile.getLineAndCharacterOfPosition(
|
|
302
|
+
statement.getStart(state.sourceFile)
|
|
267
303
|
)
|
|
268
304
|
return { line, column: character }
|
|
269
305
|
}
|
|
@@ -357,6 +393,36 @@ function collectDeclaredNames(sourceFile: ts.SourceFile): Set<string> {
|
|
|
357
393
|
return names
|
|
358
394
|
}
|
|
359
395
|
|
|
396
|
+
/** 收集从 @vobs/reactivity / @vobs/vobs 导入的 `state` 绑定名(含 `as` 别名)。 */
|
|
397
|
+
function collectStateAliases(sourceFile: ts.SourceFile): Set<string> {
|
|
398
|
+
const aliases = new Set<string>()
|
|
399
|
+
for (const statement of sourceFile.statements) {
|
|
400
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue
|
|
401
|
+
const module = statement.moduleSpecifier.text
|
|
402
|
+
if (module !== '@vobs/reactivity' && module !== '@vobs/vobs') continue
|
|
403
|
+
const clause = statement.importClause
|
|
404
|
+
if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings)) continue
|
|
405
|
+
for (const element of clause.namedBindings.elements) {
|
|
406
|
+
if (element.propertyName ? element.propertyName.text === 'state' : element.name.text === 'state') {
|
|
407
|
+
aliases.add(element.name.text)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return aliases
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** 与 collectDeclaredNames 相同,但跳过 import 声明:用于判断 helper 名是否被本地声明遮蔽。 */
|
|
415
|
+
function collectLocallyDeclaredNames(sourceFile: ts.SourceFile): Set<string> {
|
|
416
|
+
const names = new Set<string>()
|
|
417
|
+
const visit = (node: ts.Node): void => {
|
|
418
|
+
if (ts.isImportDeclaration(node)) return
|
|
419
|
+
if (ts.isIdentifier(node) && isBindingName(node)) names.add(node.text)
|
|
420
|
+
ts.forEachChild(node, visit)
|
|
421
|
+
}
|
|
422
|
+
visit(sourceFile)
|
|
423
|
+
return names
|
|
424
|
+
}
|
|
425
|
+
|
|
360
426
|
/** 标识符是否为某个声明的绑定名(import、变量、函数、参数、类成员等)。 */
|
|
361
427
|
function isBindingName(node: ts.Identifier): boolean {
|
|
362
428
|
const parent = node.parent
|
|
@@ -373,27 +439,27 @@ function isBindingName(node: ts.Identifier): boolean {
|
|
|
373
439
|
* the source never binds it; otherwise a collision-free alias is allocated and
|
|
374
440
|
* the injected import uses the same alias (`import { createElement as _vobs_createElement }`).
|
|
375
441
|
*/
|
|
376
|
-
function resolveHelperName(name: string): string {
|
|
377
|
-
const existing = helperAliases.get(name)
|
|
442
|
+
function resolveHelperName(state: CompileState, name: string): string {
|
|
443
|
+
const existing = state.helperAliases.get(name)
|
|
378
444
|
if (existing) return existing
|
|
379
445
|
let alias = name
|
|
380
|
-
if (takenNames.has(alias)) {
|
|
446
|
+
if (state.takenNames.has(alias)) {
|
|
381
447
|
alias = `_vobs_${name}`
|
|
382
448
|
let suffix = 1
|
|
383
|
-
while (takenNames.has(alias)) alias = `_vobs_${name}_${suffix++}`
|
|
449
|
+
while (state.takenNames.has(alias)) alias = `_vobs_${name}_${suffix++}`
|
|
384
450
|
}
|
|
385
|
-
helperAliases.set(name, alias)
|
|
451
|
+
state.helperAliases.set(name, alias)
|
|
386
452
|
return alias
|
|
387
453
|
}
|
|
388
454
|
|
|
389
455
|
/** Create a reference to a runtime helper in generated code, matching the injected import. */
|
|
390
|
-
function helperRef(name: string): ts.Identifier {
|
|
391
|
-
return ts.factory.createIdentifier(resolveHelperName(name))
|
|
456
|
+
function helperRef(state: CompileState, name: string): ts.Identifier {
|
|
457
|
+
return ts.factory.createIdentifier(resolveHelperName(state, name))
|
|
392
458
|
}
|
|
393
459
|
|
|
394
|
-
function createRuntimeImports(): ts.ImportDeclaration[] {
|
|
460
|
+
function createRuntimeImports(state: CompileState): ts.ImportDeclaration[] {
|
|
395
461
|
const modules = new Map<string, ts.ImportSpecifier[]>()
|
|
396
|
-
for (const [name, alias] of helperAliases) {
|
|
462
|
+
for (const [name, alias] of state.helperAliases) {
|
|
397
463
|
const module = name === 'insertResourceBoundary' ? '@vobs/resource' : '@vobs/vobs'
|
|
398
464
|
const imported = modules.get(module) ?? []
|
|
399
465
|
imported.push(ts.factory.createImportSpecifier(
|
|
@@ -414,9 +480,9 @@ function createRuntimeImports(): ts.ImportDeclaration[] {
|
|
|
414
480
|
))
|
|
415
481
|
}
|
|
416
482
|
|
|
417
|
-
function rebuildImport(node: ts.ImportDeclaration): ts.ImportDeclaration {
|
|
483
|
+
function rebuildImport(state: CompileState, node: ts.ImportDeclaration): ts.ImportDeclaration {
|
|
418
484
|
if (!ts.isStringLiteral(node.moduleSpecifier)) return node
|
|
419
|
-
return tagStatement(ts.factory.createImportDeclaration(
|
|
485
|
+
return tagStatement(state, ts.factory.createImportDeclaration(
|
|
420
486
|
node.modifiers,
|
|
421
487
|
node.importClause,
|
|
422
488
|
ts.factory.createStringLiteral(node.moduleSpecifier.text),
|
|
@@ -424,9 +490,9 @@ function rebuildImport(node: ts.ImportDeclaration): ts.ImportDeclaration {
|
|
|
424
490
|
), node)
|
|
425
491
|
}
|
|
426
492
|
|
|
427
|
-
function transformStatement(node: ts.Statement): ts.Statement {
|
|
493
|
+
function transformStatement(state: CompileState, node: ts.Statement): ts.Statement {
|
|
428
494
|
if (ts.isFunctionDeclaration(node) && node.body) {
|
|
429
|
-
return tagStatement(ts.factory.updateFunctionDeclaration(
|
|
495
|
+
return tagStatement(state, ts.factory.updateFunctionDeclaration(
|
|
430
496
|
node,
|
|
431
497
|
node.modifiers,
|
|
432
498
|
node.asteriskToken,
|
|
@@ -434,48 +500,84 @@ function transformStatement(node: ts.Statement): ts.Statement {
|
|
|
434
500
|
node.typeParameters,
|
|
435
501
|
node.parameters,
|
|
436
502
|
node.type,
|
|
437
|
-
transformBlock(node.body)
|
|
503
|
+
transformBlock(state, node.body)
|
|
438
504
|
), node)
|
|
439
505
|
}
|
|
440
506
|
|
|
441
|
-
if (ts.isVariableStatement(node)) return transformVariableStatement(node)
|
|
507
|
+
if (ts.isVariableStatement(node)) return transformVariableStatement(state, node)
|
|
442
508
|
if (ts.isExportAssignment(node) && containsJsx(node.expression)) {
|
|
443
|
-
return tagStatement(ts.factory.updateExportAssignment(node, node.modifiers, transformEmbeddedExpression(node.expression)), node)
|
|
509
|
+
return tagStatement(state, ts.factory.updateExportAssignment(node, node.modifiers, transformEmbeddedExpression(state, node.expression)), node)
|
|
444
510
|
}
|
|
445
511
|
if (ts.isExpressionStatement(node) && containsJsx(node.expression)) {
|
|
446
|
-
return tagStatement(ts.factory.updateExpressionStatement(node, transformEmbeddedExpression(node.expression)), node)
|
|
512
|
+
return tagStatement(state, ts.factory.updateExpressionStatement(node, transformEmbeddedExpression(state, node.expression)), node)
|
|
447
513
|
}
|
|
448
514
|
if (ts.isReturnStatement(node) && node.expression && containsJsx(node.expression)) {
|
|
449
|
-
return tagStatement(ts.factory.updateReturnStatement(node, transformEmbeddedExpression(node.expression)), node)
|
|
515
|
+
return tagStatement(state, ts.factory.updateReturnStatement(node, transformEmbeddedExpression(state, node.expression)), node)
|
|
450
516
|
}
|
|
451
517
|
return node
|
|
452
518
|
}
|
|
453
519
|
|
|
454
|
-
function transformVariableStatement(node: ts.VariableStatement): ts.VariableStatement {
|
|
520
|
+
function transformVariableStatement(state: CompileState, node: ts.VariableStatement): ts.VariableStatement {
|
|
521
|
+
let changed = false
|
|
455
522
|
const declarations = node.declarationList.declarations.map(declaration => {
|
|
456
523
|
const initializer = declaration.initializer
|
|
457
|
-
if (!initializer
|
|
524
|
+
if (!initializer) return declaration
|
|
525
|
+
|
|
526
|
+
let nextInitializer = inferStateDebugName(state, declaration, initializer) ?? initializer
|
|
527
|
+
if (containsJsx(nextInitializer)) {
|
|
528
|
+
nextInitializer = transformEmbeddedExpression(state, nextInitializer)
|
|
529
|
+
}
|
|
530
|
+
if (nextInitializer === initializer) return declaration
|
|
458
531
|
|
|
532
|
+
changed = true
|
|
459
533
|
return ts.factory.updateVariableDeclaration(
|
|
460
534
|
declaration,
|
|
461
535
|
declaration.name,
|
|
462
536
|
declaration.exclamationToken,
|
|
463
537
|
declaration.type,
|
|
464
|
-
|
|
538
|
+
nextInitializer
|
|
465
539
|
)
|
|
466
540
|
})
|
|
467
541
|
|
|
468
|
-
if (
|
|
469
|
-
return node
|
|
470
|
-
}
|
|
542
|
+
if (!changed) return node
|
|
471
543
|
|
|
472
|
-
return tagStatement(ts.factory.updateVariableStatement(
|
|
544
|
+
return tagStatement(state, ts.factory.updateVariableStatement(
|
|
473
545
|
node,
|
|
474
546
|
node.modifiers,
|
|
475
547
|
ts.factory.updateVariableDeclarationList(node.declarationList, declarations)
|
|
476
548
|
), node)
|
|
477
549
|
}
|
|
478
550
|
|
|
551
|
+
/**
|
|
552
|
+
* `const name = state(initial)` 在未显式传入 debugName 时从变量名推断:
|
|
553
|
+
* `const username = state('')` → `state('', 'username')`,使 DevTools 信号名称与源码命名一致。
|
|
554
|
+
* 仅当 `state` 确认来自 @vobs/reactivity / @vobs/vobs、未被本地声明遮蔽、
|
|
555
|
+
* 且调用只带一个参数时启用;其余形态保持原样。
|
|
556
|
+
*/
|
|
557
|
+
function inferStateDebugName(
|
|
558
|
+
state: CompileState,
|
|
559
|
+
declaration: ts.VariableDeclaration,
|
|
560
|
+
initializer: ts.Expression
|
|
561
|
+
): ts.Expression | undefined {
|
|
562
|
+
if (state.stateAliases.size === 0) return undefined
|
|
563
|
+
if (!ts.isIdentifier(declaration.name)) return undefined
|
|
564
|
+
|
|
565
|
+
let call = initializer
|
|
566
|
+
while (ts.isParenthesizedExpression(call) || ts.isAsExpression(call) || ts.isTypeAssertionExpression(call) || ts.isSatisfiesExpression(call)) {
|
|
567
|
+
call = call.expression
|
|
568
|
+
}
|
|
569
|
+
if (!ts.isCallExpression(call)) return undefined
|
|
570
|
+
const callee = call.expression
|
|
571
|
+
if (!ts.isIdentifier(callee) || !state.stateAliases.has(callee.text)) return undefined
|
|
572
|
+
if (state.localBindings.has(callee.text)) return undefined
|
|
573
|
+
if (call.arguments.length !== 1) return undefined
|
|
574
|
+
|
|
575
|
+
return ts.factory.createCallExpression(callee, call.typeArguments, [
|
|
576
|
+
...call.arguments,
|
|
577
|
+
ts.factory.createStringLiteral(declaration.name.text)
|
|
578
|
+
])
|
|
579
|
+
}
|
|
580
|
+
|
|
479
581
|
function containsJsx(expression: ts.Expression): boolean {
|
|
480
582
|
let found = false
|
|
481
583
|
const visit = (node: ts.Node): void => {
|
|
@@ -489,14 +591,14 @@ function containsJsx(expression: ts.Expression): boolean {
|
|
|
489
591
|
return found
|
|
490
592
|
}
|
|
491
593
|
|
|
492
|
-
function transformBlock(block: ts.Block): ts.Block {
|
|
594
|
+
function transformBlock(state: CompileState, block: ts.Block): ts.Block {
|
|
493
595
|
const statements = block.statements.map(statement => {
|
|
494
|
-
if (!ts.isReturnStatement(statement) || !statement.expression) return transformStatement(statement)
|
|
596
|
+
if (!ts.isReturnStatement(statement) || !statement.expression) return transformStatement(state, statement)
|
|
495
597
|
const expression = ts.isParenthesizedExpression(statement.expression)
|
|
496
598
|
? statement.expression.expression
|
|
497
599
|
: statement.expression
|
|
498
600
|
return isJsxExpression(expression)
|
|
499
|
-
? tagStatement(ts.factory.updateReturnStatement(statement, transformJsxExpression(expression)), statement)
|
|
601
|
+
? tagStatement(state, ts.factory.updateReturnStatement(statement, transformJsxExpression(state, expression)), statement)
|
|
500
602
|
: statement
|
|
501
603
|
})
|
|
502
604
|
return ts.factory.updateBlock(block, statements)
|
|
@@ -506,61 +608,71 @@ function isJsxExpression(node: ts.Expression): node is ts.JsxElement | ts.JsxSel
|
|
|
506
608
|
return ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)
|
|
507
609
|
}
|
|
508
610
|
|
|
509
|
-
function transformJsxExpression(node: ts.JsxElement | ts.JsxSelfClosingElement | ts.JsxFragment): ts.Expression {
|
|
510
|
-
if (ts.isJsxFragment(node)) return transformFragment(node.children)
|
|
611
|
+
function transformJsxExpression(state: CompileState, node: ts.JsxElement | ts.JsxSelfClosingElement | ts.JsxFragment): ts.Expression {
|
|
612
|
+
if (ts.isJsxFragment(node)) return transformFragment(state, node.children)
|
|
511
613
|
if (ts.isJsxElement(node)) {
|
|
512
|
-
return transformElement(node, node.openingElement.tagName, node.openingElement.attributes, node.children)
|
|
614
|
+
return transformElement(state, node, node.openingElement.tagName, node.openingElement.attributes, node.children)
|
|
513
615
|
}
|
|
514
|
-
return transformElement(node, node.tagName, node.attributes, [])
|
|
616
|
+
return transformElement(state, node, node.tagName, node.attributes, [])
|
|
515
617
|
}
|
|
516
618
|
|
|
517
619
|
function transformElement(
|
|
620
|
+
state: CompileState,
|
|
518
621
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
519
622
|
tagName: ts.JsxTagNameExpression,
|
|
520
623
|
attributes: ts.JsxAttributes,
|
|
521
624
|
children: readonly ts.JsxChild[]
|
|
522
625
|
): ts.Expression {
|
|
523
|
-
if (isFragmentTag(tagName)) return transformFragment(children)
|
|
626
|
+
if (isFragmentTag(tagName)) return transformFragment(state, children)
|
|
524
627
|
if (!ts.isIdentifier(tagName)) {
|
|
525
628
|
// <Foo.Bar>、<svg:rect> 等形态此前会静默编译成无效 DOM 标签(createElement("Foo.Bar"))。
|
|
526
629
|
// 报结构化诊断后按原路径继续,保证产物结构稳定;compile()/Vite 插件会因 error 诊断直接失败。
|
|
527
|
-
reportUnsupportedTag(tagName)
|
|
630
|
+
reportUnsupportedTag(state, tagName)
|
|
528
631
|
}
|
|
529
632
|
if (ts.isIdentifier(tagName) && tagName.text === 'ResourceBoundary') {
|
|
530
|
-
return transformResourceBoundary(node, attributes, children)
|
|
633
|
+
return transformResourceBoundary(state, node, attributes, children)
|
|
531
634
|
}
|
|
532
635
|
if (ts.isIdentifier(tagName) && tagName.text === 'AsyncBoundary') {
|
|
533
|
-
return transformAsyncBoundary(node, attributes, children)
|
|
636
|
+
return transformAsyncBoundary(state, node, attributes, children)
|
|
534
637
|
}
|
|
535
638
|
if (ts.isIdentifier(tagName) && tagName.text === 'ErrorBoundary') {
|
|
536
|
-
return transformErrorBoundary(node, attributes, children)
|
|
639
|
+
return transformErrorBoundary(state, node, attributes, children)
|
|
537
640
|
}
|
|
538
641
|
if (ts.isIdentifier(tagName) && tagName.text === 'Profiler') {
|
|
539
|
-
return transformProfiler(node, attributes, children)
|
|
642
|
+
return transformProfiler(state, node, attributes, children)
|
|
540
643
|
}
|
|
541
644
|
if (ts.isIdentifier(tagName) && /^[A-Z]/.test(tagName.text)) {
|
|
645
|
+
const args: ts.Expression[] = [
|
|
646
|
+
ts.factory.createCallExpression(helperRef(state, 'resolveComponent'), undefined, [
|
|
647
|
+
tagName,
|
|
648
|
+
ts.factory.createStringLiteral(state.filename),
|
|
649
|
+
ts.factory.createStringLiteral(tagName.text)
|
|
650
|
+
]),
|
|
651
|
+
createComponentProps(state, attributes, children)
|
|
652
|
+
]
|
|
653
|
+
// 源码位置仅用于错误定位与 DevTools;生产构建可整体剔除(错误仍带组件名,定位走 source map)。
|
|
654
|
+
if (state.sourceLocation) args.push(createSourceLocation(tagName))
|
|
655
|
+
return ts.factory.createCallExpression(helperRef(state, 'createComponent'), undefined, args)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// 静态模板提升:完全静态的 DOM 子树(无事件/动态绑定/spread/property 属性)序列化为
|
|
659
|
+
// 模块级模板,运行时一次 cloneNode 替代 createElement + setStaticProps + 逐子插入。
|
|
660
|
+
if (isStaticElement(tagName, attributes, children)) {
|
|
542
661
|
return ts.factory.createCallExpression(
|
|
543
|
-
helperRef('
|
|
662
|
+
helperRef(state, 'cloneTemplate'),
|
|
544
663
|
undefined,
|
|
545
|
-
[
|
|
546
|
-
ts.factory.createCallExpression(helperRef('resolveComponent'), undefined, [
|
|
547
|
-
tagName,
|
|
548
|
-
ts.factory.createStringLiteral(currentFilename),
|
|
549
|
-
ts.factory.createStringLiteral(tagName.text)
|
|
550
|
-
]),
|
|
551
|
-
createComponentProps(attributes, children),
|
|
552
|
-
createSourceLocation(tagName)
|
|
553
|
-
]
|
|
664
|
+
[registerTemplate(state, serializeStaticHtml(node))]
|
|
554
665
|
)
|
|
555
666
|
}
|
|
556
667
|
|
|
557
668
|
const elementName = tagName.getText()
|
|
558
|
-
const elementId = nextIdentifier('_el')
|
|
669
|
+
const elementId = nextIdentifier(state, '_el')
|
|
559
670
|
const statements: ts.Statement[] = [
|
|
560
671
|
createConstStatement(
|
|
672
|
+
state,
|
|
561
673
|
elementId,
|
|
562
674
|
ts.factory.createCallExpression(
|
|
563
|
-
helperRef('createElement'),
|
|
675
|
+
helperRef(state, 'createElement'),
|
|
564
676
|
undefined,
|
|
565
677
|
[ts.factory.createStringLiteral(elementName)]
|
|
566
678
|
),
|
|
@@ -568,9 +680,9 @@ function transformElement(
|
|
|
568
680
|
)
|
|
569
681
|
]
|
|
570
682
|
|
|
571
|
-
appendAttributes(statements, elementId, attributes)
|
|
572
|
-
appendChildren(statements, elementId, children)
|
|
573
|
-
statements.push(tagStatement(ts.factory.createReturnStatement(elementId), node))
|
|
683
|
+
appendAttributes(state, statements, elementId, attributes)
|
|
684
|
+
appendChildren(state, statements, elementId, children)
|
|
685
|
+
statements.push(tagStatement(state, ts.factory.createReturnStatement(elementId), node))
|
|
574
686
|
|
|
575
687
|
return ts.factory.createCallExpression(
|
|
576
688
|
ts.factory.createArrowFunction(
|
|
@@ -590,13 +702,13 @@ function isFragmentTag(tagName: ts.JsxTagNameExpression): boolean {
|
|
|
590
702
|
return tagName.getText() === 'Fragment' || tagName.getText() === 'Vobs.Fragment'
|
|
591
703
|
}
|
|
592
704
|
|
|
593
|
-
function transformFragment(children: readonly ts.JsxChild[]): ts.Expression {
|
|
594
|
-
const parent = nextIdentifier('_fragmentParent')
|
|
595
|
-
const anchor = nextIdentifier('_fragmentAnchor')
|
|
705
|
+
function transformFragment(state: CompileState, children: readonly ts.JsxChild[]): ts.Expression {
|
|
706
|
+
const parent = nextIdentifier(state, '_fragmentParent')
|
|
707
|
+
const anchor = nextIdentifier(state, '_fragmentAnchor')
|
|
596
708
|
const statements: ts.Statement[] = []
|
|
597
|
-
appendChildren(statements, parent, children, anchor)
|
|
709
|
+
appendChildren(state, statements, parent, children, anchor)
|
|
598
710
|
return ts.factory.createCallExpression(
|
|
599
|
-
helperRef('createFragment'),
|
|
711
|
+
helperRef(state, 'createFragment'),
|
|
600
712
|
undefined,
|
|
601
713
|
[ts.factory.createArrowFunction(
|
|
602
714
|
undefined,
|
|
@@ -613,6 +725,7 @@ function transformFragment(children: readonly ts.JsxChild[]): ts.Expression {
|
|
|
613
725
|
}
|
|
614
726
|
|
|
615
727
|
function transformResourceBoundary(
|
|
728
|
+
state: CompileState,
|
|
616
729
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
617
730
|
attributes: ts.JsxAttributes,
|
|
618
731
|
children: readonly ts.JsxChild[]
|
|
@@ -620,29 +733,31 @@ function transformResourceBoundary(
|
|
|
620
733
|
const resource = getAttributeExpression(attributes, 'resource')
|
|
621
734
|
if (!resource) throw new VobsError({ code: 'VOBS_C002', layer: 'compiler', message: 'ResourceBoundary 必须提供 resource 属性', fix: '为 ResourceBoundary 添加 resource={resource}。' })
|
|
622
735
|
const options: ts.ObjectLiteralElementLike[] = [
|
|
623
|
-
ts.factory.createPropertyAssignment('resource', transformEmbeddedExpression(resource)),
|
|
624
|
-
ts.factory.createPropertyAssignment('children', createBoundaryFactory(children))
|
|
736
|
+
ts.factory.createPropertyAssignment('resource', transformEmbeddedExpression(state, resource)),
|
|
737
|
+
ts.factory.createPropertyAssignment('children', createBoundaryFactory(state, children))
|
|
625
738
|
]
|
|
626
|
-
appendBoundaryOptionalProperty(options, attributes, 'loading')
|
|
627
|
-
appendBoundaryOptionalProperty(options, attributes, 'empty')
|
|
628
|
-
appendBoundaryOptionalProperty(options, attributes, 'fallback')
|
|
629
|
-
return createBoundaryFragment(node, 'insertResourceBoundary', options)
|
|
739
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'loading')
|
|
740
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'empty')
|
|
741
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'fallback')
|
|
742
|
+
return createBoundaryFragment(state, node, 'insertResourceBoundary', options)
|
|
630
743
|
}
|
|
631
744
|
|
|
632
745
|
function transformErrorBoundary(
|
|
746
|
+
state: CompileState,
|
|
633
747
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
634
748
|
attributes: ts.JsxAttributes,
|
|
635
749
|
children: readonly ts.JsxChild[]
|
|
636
750
|
): ts.Expression {
|
|
637
751
|
const fallback = getAttributeExpression(attributes, 'fallback')
|
|
638
752
|
if (!fallback) throw new VobsError({ code: 'VOBS_C002', layer: 'compiler', message: 'ErrorBoundary 必须提供 fallback 属性', fix: '为 ErrorBoundary 添加 fallback={(error, retry) => ...}。' })
|
|
639
|
-
return createBoundaryFragment(node, 'insertErrorBoundary', [
|
|
640
|
-
ts.factory.createPropertyAssignment('children', createBoundaryFactory(children)),
|
|
641
|
-
ts.factory.createPropertyAssignment('fallback', transformEmbeddedExpression(fallback))
|
|
753
|
+
return createBoundaryFragment(state, node, 'insertErrorBoundary', [
|
|
754
|
+
ts.factory.createPropertyAssignment('children', createBoundaryFactory(state, children)),
|
|
755
|
+
ts.factory.createPropertyAssignment('fallback', transformEmbeddedExpression(state, fallback))
|
|
642
756
|
])
|
|
643
757
|
}
|
|
644
758
|
|
|
645
759
|
function transformAsyncBoundary(
|
|
760
|
+
state: CompileState,
|
|
646
761
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
647
762
|
attributes: ts.JsxAttributes,
|
|
648
763
|
children: readonly ts.JsxChild[]
|
|
@@ -650,17 +765,18 @@ function transformAsyncBoundary(
|
|
|
650
765
|
const promise = getAttributeExpression(attributes, 'promise')
|
|
651
766
|
if (!promise) throw new VobsError({ code: 'VOBS_C002', layer: 'compiler', message: 'AsyncBoundary 必须提供 promise 属性', fix: '为 AsyncBoundary 添加 promise={promise}。' })
|
|
652
767
|
const options: ts.ObjectLiteralElementLike[] = [
|
|
653
|
-
ts.factory.createPropertyAssignment('promise', transformEmbeddedExpression(promise)),
|
|
654
|
-
ts.factory.createPropertyAssignment('children', createAsyncFactory(children))
|
|
768
|
+
ts.factory.createPropertyAssignment('promise', transformEmbeddedExpression(state, promise)),
|
|
769
|
+
ts.factory.createPropertyAssignment('children', createAsyncFactory(state, children))
|
|
655
770
|
]
|
|
656
|
-
appendBoundaryOptionalProperty(options, attributes, 'loading')
|
|
657
|
-
appendBoundaryOptionalProperty(options, attributes, 'fallback')
|
|
771
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'loading')
|
|
772
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'fallback')
|
|
658
773
|
const resetKey = getAttributeExpression(attributes, 'resetKey')
|
|
659
774
|
if (resetKey) options.push(ts.factory.createPropertyAssignment('resetKey', createGetter(resetKey)))
|
|
660
|
-
return createBoundaryFragment(node, 'insertAsyncBoundary', options)
|
|
775
|
+
return createBoundaryFragment(state, node, 'insertAsyncBoundary', options)
|
|
661
776
|
}
|
|
662
777
|
|
|
663
778
|
function transformProfiler(
|
|
779
|
+
state: CompileState,
|
|
664
780
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
665
781
|
attributes: ts.JsxAttributes,
|
|
666
782
|
children: readonly ts.JsxChild[]
|
|
@@ -673,22 +789,23 @@ function transformProfiler(
|
|
|
673
789
|
: null
|
|
674
790
|
if (!id) throw new VobsError({ code: 'VOBS_C002', layer: 'compiler', message: 'Profiler 必须提供 id 属性', fix: '为 Profiler 添加 id="ComponentName"。' })
|
|
675
791
|
const options: ts.ObjectLiteralElementLike[] = [
|
|
676
|
-
ts.factory.createPropertyAssignment('id', transformEmbeddedExpression(id)),
|
|
677
|
-
ts.factory.createPropertyAssignment('children', createBoundaryFactory(children))
|
|
792
|
+
ts.factory.createPropertyAssignment('id', transformEmbeddedExpression(state, id)),
|
|
793
|
+
ts.factory.createPropertyAssignment('children', createBoundaryFactory(state, children))
|
|
678
794
|
]
|
|
679
|
-
appendBoundaryOptionalProperty(options, attributes, 'onRender')
|
|
680
|
-
return createBoundaryFragment(node, 'insertProfiler', options)
|
|
795
|
+
appendBoundaryOptionalProperty(state, options, attributes, 'onRender')
|
|
796
|
+
return createBoundaryFragment(state, node, 'insertProfiler', options)
|
|
681
797
|
}
|
|
682
798
|
|
|
683
799
|
function createBoundaryFragment(
|
|
800
|
+
state: CompileState,
|
|
684
801
|
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
685
802
|
helper: 'insertResourceBoundary' | 'insertErrorBoundary' | 'insertAsyncBoundary' | 'insertProfiler',
|
|
686
803
|
options: readonly ts.ObjectLiteralElementLike[]
|
|
687
804
|
): ts.Expression {
|
|
688
|
-
const parent = nextIdentifier('_boundaryParent')
|
|
689
|
-
const anchor = nextIdentifier('_boundaryAnchor')
|
|
805
|
+
const parent = nextIdentifier(state, '_boundaryParent')
|
|
806
|
+
const anchor = nextIdentifier(state, '_boundaryAnchor')
|
|
690
807
|
return ts.factory.createCallExpression(
|
|
691
|
-
helperRef('createFragment'),
|
|
808
|
+
helperRef(state, 'createFragment'),
|
|
692
809
|
undefined,
|
|
693
810
|
[ts.factory.createArrowFunction(
|
|
694
811
|
undefined,
|
|
@@ -699,7 +816,7 @@ function createBoundaryFragment(
|
|
|
699
816
|
],
|
|
700
817
|
undefined,
|
|
701
818
|
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
702
|
-
ts.factory.createBlock([callStatement(helper, [
|
|
819
|
+
ts.factory.createBlock([callStatement(state, helper, [
|
|
703
820
|
parent,
|
|
704
821
|
anchor,
|
|
705
822
|
ts.factory.createObjectLiteralExpression(options, true)
|
|
@@ -708,8 +825,8 @@ function createBoundaryFragment(
|
|
|
708
825
|
)
|
|
709
826
|
}
|
|
710
827
|
|
|
711
|
-
function createBoundaryFactory(children: readonly ts.JsxChild[]): ts.ArrowFunction {
|
|
712
|
-
const content = transformFragment(children)
|
|
828
|
+
function createBoundaryFactory(state: CompileState, children: readonly ts.JsxChild[]): ts.ArrowFunction {
|
|
829
|
+
const content = transformFragment(state, children)
|
|
713
830
|
return ts.factory.createArrowFunction(
|
|
714
831
|
undefined,
|
|
715
832
|
undefined,
|
|
@@ -720,29 +837,30 @@ function createBoundaryFactory(children: readonly ts.JsxChild[]): ts.ArrowFuncti
|
|
|
720
837
|
)
|
|
721
838
|
}
|
|
722
839
|
|
|
723
|
-
function createAsyncFactory(children: readonly ts.JsxChild[]): ts.ArrowFunction {
|
|
840
|
+
function createAsyncFactory(state: CompileState, children: readonly ts.JsxChild[]): ts.ArrowFunction {
|
|
724
841
|
const value = ts.factory.createIdentifier('value')
|
|
725
842
|
const expressionChild = children.length === 1 && children[0].kind === ts.SyntaxKind.JsxExpression
|
|
726
843
|
? (children[0] as ts.JsxExpression).expression
|
|
727
844
|
: undefined
|
|
728
845
|
if (expressionChild && ts.isArrowFunction(expressionChild)) {
|
|
729
|
-
const transformed = transformEmbeddedExpression(expressionChild)
|
|
846
|
+
const transformed = transformEmbeddedExpression(state, expressionChild)
|
|
730
847
|
return transformed as ts.ArrowFunction
|
|
731
848
|
}
|
|
732
|
-
const content = transformFragment(children)
|
|
849
|
+
const content = transformFragment(state, children)
|
|
733
850
|
return ts.factory.createArrowFunction(undefined, undefined, [
|
|
734
851
|
ts.factory.createParameterDeclaration(undefined, undefined, value)
|
|
735
852
|
], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), content)
|
|
736
853
|
}
|
|
737
854
|
|
|
738
855
|
function appendBoundaryOptionalProperty(
|
|
856
|
+
state: CompileState,
|
|
739
857
|
properties: ts.ObjectLiteralElementLike[],
|
|
740
858
|
attributes: ts.JsxAttributes,
|
|
741
859
|
name: string
|
|
742
860
|
): void {
|
|
743
861
|
const expression = getAttributeExpression(attributes, name)
|
|
744
862
|
if (expression) {
|
|
745
|
-
const transformed = transformEmbeddedExpression(expression)
|
|
863
|
+
const transformed = transformEmbeddedExpression(state, expression)
|
|
746
864
|
const value = isJsxExpression(unwrapExpression(expression))
|
|
747
865
|
? createGetter(transformed)
|
|
748
866
|
: transformed
|
|
@@ -760,11 +878,11 @@ function getAttributeExpression(attributes: ts.JsxAttributes, name: string): ts.
|
|
|
760
878
|
return null
|
|
761
879
|
}
|
|
762
880
|
|
|
763
|
-
function transformEmbeddedExpression(expression: ts.Expression): ts.Expression {
|
|
881
|
+
function transformEmbeddedExpression(state: CompileState, expression: ts.Expression): ts.Expression {
|
|
764
882
|
const result = ts.transform(expression, [context => root => {
|
|
765
883
|
const visit: ts.Visitor = node => {
|
|
766
884
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) {
|
|
767
|
-
return transformJsxExpression(node)
|
|
885
|
+
return transformJsxExpression(state, node)
|
|
768
886
|
}
|
|
769
887
|
return ts.visitEachChild(node, visit, context)
|
|
770
888
|
}
|
|
@@ -777,7 +895,125 @@ function transformEmbeddedExpression(expression: ts.Expression): ts.Expression {
|
|
|
777
895
|
}
|
|
778
896
|
}
|
|
779
897
|
|
|
898
|
+
/**
|
|
899
|
+
* 静态元素判定:DOM 标签 + 全部属性为字符串字面量或无值 + 全部子节点为文本或递归静态元素。
|
|
900
|
+
* 保守排除项(语义或序列化等价性无把握,走原路径):
|
|
901
|
+
* - property 属性(value/checked/disabled 等):HTML attribute 与 setProperty 初始语义存在差异;
|
|
902
|
+
* - 事件(on*)、ref、spread、key:本身是动态行为;
|
|
903
|
+
* - 嵌套组件/Fragment/Boundary:不是纯 DOM 子树。
|
|
904
|
+
*/
|
|
905
|
+
function isStaticElement(
|
|
906
|
+
tagName: ts.JsxTagNameExpression,
|
|
907
|
+
attributes: ts.JsxAttributes,
|
|
908
|
+
children: readonly ts.JsxChild[]
|
|
909
|
+
): boolean {
|
|
910
|
+
if (!ts.isIdentifier(tagName) || !/^[a-z]/.test(tagName.text)) return false
|
|
911
|
+
for (const attribute of attributes.properties) {
|
|
912
|
+
if (ts.isJsxSpreadAttribute(attribute)) return false
|
|
913
|
+
if (!ts.isJsxAttribute(attribute)) return false
|
|
914
|
+
const name = attribute.name.getText()
|
|
915
|
+
if (name === 'key' || name === 'ref' || name.startsWith('on')) return false
|
|
916
|
+
if (isPropertyAttribute(name)) return false
|
|
917
|
+
const initializer = attribute.initializer
|
|
918
|
+
if (initializer && !ts.isStringLiteral(initializer)) return false
|
|
919
|
+
}
|
|
920
|
+
for (const child of children) {
|
|
921
|
+
if (ts.isJsxText(child)) continue
|
|
922
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
|
|
923
|
+
const nested = ts.isJsxElement(child)
|
|
924
|
+
? { tagName: child.openingElement.tagName, attributes: child.openingElement.attributes, children: child.children }
|
|
925
|
+
: { tagName: child.tagName, attributes: child.attributes, children: [] as readonly ts.JsxChild[] }
|
|
926
|
+
if (!isStaticElement(nested.tagName, nested.attributes, nested.children)) return false
|
|
927
|
+
continue
|
|
928
|
+
}
|
|
929
|
+
return false
|
|
930
|
+
}
|
|
931
|
+
return true
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/** Serialize a fully-static JSX element to HTML, preserving the compiler's text normalization. */
|
|
935
|
+
function serializeStaticHtml(node: ts.JsxElement | ts.JsxSelfClosingElement): string {
|
|
936
|
+
const { tagName, attributes, children } = ts.isJsxElement(node)
|
|
937
|
+
? { tagName: node.openingElement.tagName, attributes: node.openingElement.attributes, children: node.children }
|
|
938
|
+
: { tagName: node.tagName, attributes: node.attributes, children: [] as readonly ts.JsxChild[] }
|
|
939
|
+
return serializeStaticElement(tagName, attributes, children)
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function serializeStaticElement(
|
|
943
|
+
tagName: ts.JsxTagNameExpression,
|
|
944
|
+
attributes: ts.JsxAttributes,
|
|
945
|
+
children: readonly ts.JsxChild[]
|
|
946
|
+
): string {
|
|
947
|
+
const name = tagName.getText()
|
|
948
|
+
let html = `<${name}`
|
|
949
|
+
for (const attribute of attributes.properties) {
|
|
950
|
+
if (!ts.isJsxAttribute(attribute)) continue
|
|
951
|
+
const attributeName = attribute.name.getText() === 'className' ? 'class' : attribute.name.getText()
|
|
952
|
+
const initializer = attribute.initializer
|
|
953
|
+
if (!initializer) {
|
|
954
|
+
html += ` ${attributeName}=""`
|
|
955
|
+
continue
|
|
956
|
+
}
|
|
957
|
+
if (ts.isStringLiteral(initializer)) {
|
|
958
|
+
html += ` ${attributeName}="${escapeHtmlAttribute(initializer.text)}"`
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
html += '>'
|
|
962
|
+
|
|
963
|
+
for (const child of children) {
|
|
964
|
+
if (ts.isJsxText(child)) {
|
|
965
|
+
// 与 appendChildren 的文本规范化保持一致,保证提升前后 DOM 文本逐字相同。
|
|
966
|
+
const text = child.text.replace(/\s+/g, ' ').trimStart()
|
|
967
|
+
if (text.trim()) html += escapeHtmlText(text)
|
|
968
|
+
continue
|
|
969
|
+
}
|
|
970
|
+
if (ts.isJsxElement(child)) {
|
|
971
|
+
html += serializeStaticElement(
|
|
972
|
+
child.openingElement.tagName,
|
|
973
|
+
child.openingElement.attributes,
|
|
974
|
+
child.children
|
|
975
|
+
)
|
|
976
|
+
continue
|
|
977
|
+
}
|
|
978
|
+
if (ts.isJsxSelfClosingElement(child)) {
|
|
979
|
+
html += serializeStaticElement(child.tagName, child.attributes, [])
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
html += `</${name}>`
|
|
983
|
+
return html
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function escapeHtmlAttribute(value: string): string {
|
|
987
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function escapeHtmlText(value: string): string {
|
|
991
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/** Register a hoisted template declaration; identical HTML shares one declaration. */
|
|
995
|
+
function registerTemplate(state: CompileState, html: string): ts.Expression {
|
|
996
|
+
const existing = state.templates.get(html)
|
|
997
|
+
if (existing) return existing
|
|
998
|
+
const identifier = nextIdentifier(state, '_tpl')
|
|
999
|
+
state.templates.set(html, identifier)
|
|
1000
|
+
return identifier
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function createTemplateDeclarations(state: CompileState): ts.Statement[] {
|
|
1004
|
+
return [...state.templates.entries()].map(([html, identifier]) =>
|
|
1005
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
1006
|
+
ts.factory.createVariableDeclaration(identifier, undefined, undefined, ts.factory.createCallExpression(
|
|
1007
|
+
helperRef(state, 'createTemplate'),
|
|
1008
|
+
undefined,
|
|
1009
|
+
[ts.factory.createStringLiteral(html)]
|
|
1010
|
+
))
|
|
1011
|
+
], ts.NodeFlags.Const))
|
|
1012
|
+
)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
780
1015
|
function appendAttributes(
|
|
1016
|
+
state: CompileState,
|
|
781
1017
|
statements: ts.Statement[],
|
|
782
1018
|
element: ts.Identifier,
|
|
783
1019
|
attributes: ts.JsxAttributes
|
|
@@ -786,7 +1022,7 @@ function appendAttributes(
|
|
|
786
1022
|
const hasSpread = attributes.properties.some(attribute => ts.isJsxSpreadAttribute(attribute))
|
|
787
1023
|
for (const attribute of attributes.properties) {
|
|
788
1024
|
if (ts.isJsxSpreadAttribute(attribute)) {
|
|
789
|
-
statements.push(callStatement('spreadProps', [element, transformEmbeddedExpression(attribute.expression)], attribute))
|
|
1025
|
+
statements.push(callStatement(state, 'spreadProps', [element, transformEmbeddedExpression(state, attribute.expression)], attribute))
|
|
790
1026
|
continue
|
|
791
1027
|
}
|
|
792
1028
|
if (!ts.isJsxAttribute(attribute)) continue
|
|
@@ -795,14 +1031,14 @@ function appendAttributes(
|
|
|
795
1031
|
if (name === 'ref') {
|
|
796
1032
|
const initializer = attribute.initializer
|
|
797
1033
|
if (initializer && ts.isJsxExpression(initializer) && initializer.expression) {
|
|
798
|
-
statements.push(callStatement('setRef', [element, transformEmbeddedExpression(initializer.expression)], attribute))
|
|
1034
|
+
statements.push(callStatement(state, 'setRef', [element, transformEmbeddedExpression(state, initializer.expression)], attribute))
|
|
799
1035
|
}
|
|
800
1036
|
continue
|
|
801
1037
|
}
|
|
802
1038
|
const initializer = attribute.initializer
|
|
803
1039
|
|
|
804
1040
|
if (name.startsWith('on') && initializer && ts.isJsxExpression(initializer) && initializer.expression) {
|
|
805
|
-
statements.push(callStatement('addEventListener', [
|
|
1041
|
+
statements.push(callStatement(state, 'addEventListener', [
|
|
806
1042
|
element,
|
|
807
1043
|
ts.factory.createStringLiteral(name.slice(2).toLowerCase()),
|
|
808
1044
|
initializer.expression
|
|
@@ -812,7 +1048,7 @@ function appendAttributes(
|
|
|
812
1048
|
|
|
813
1049
|
if (!initializer) {
|
|
814
1050
|
if (hasSpread) {
|
|
815
|
-
statements.push(callStatement(isPropertyAttribute(name) ? 'setProperty' : 'setAttribute', [element, ts.factory.createStringLiteral(isPropertyAttribute(name) ? name : name === 'className' ? 'class' : name), isPropertyAttribute(name) ? ts.factory.createTrue() : ts.factory.createStringLiteral('')], attribute))
|
|
1051
|
+
statements.push(callStatement(state, isPropertyAttribute(name) ? 'setProperty' : 'setAttribute', [element, ts.factory.createStringLiteral(isPropertyAttribute(name) ? name : name === 'className' ? 'class' : name), isPropertyAttribute(name) ? ts.factory.createTrue() : ts.factory.createStringLiteral('')], attribute))
|
|
816
1052
|
continue
|
|
817
1053
|
}
|
|
818
1054
|
if (isPropertyAttribute(name)) staticProps.push(createStaticProperty(name, ts.factory.createTrue()))
|
|
@@ -821,7 +1057,7 @@ function appendAttributes(
|
|
|
821
1057
|
}
|
|
822
1058
|
if (ts.isStringLiteral(initializer)) {
|
|
823
1059
|
if (hasSpread) {
|
|
824
|
-
statements.push(callStatement(isPropertyAttribute(name) ? 'setProperty' : 'setAttribute', [element, ts.factory.createStringLiteral(isPropertyAttribute(name) ? name : name === 'className' ? 'class' : name), ts.factory.createStringLiteral(initializer.text)], attribute))
|
|
1060
|
+
statements.push(callStatement(state, isPropertyAttribute(name) ? 'setProperty' : 'setAttribute', [element, ts.factory.createStringLiteral(isPropertyAttribute(name) ? name : name === 'className' ? 'class' : name), ts.factory.createStringLiteral(initializer.text)], attribute))
|
|
825
1061
|
continue
|
|
826
1062
|
}
|
|
827
1063
|
staticProps.push(createStaticProperty(isPropertyAttribute(name) ? name : name === 'className' ? 'class' : name,
|
|
@@ -832,14 +1068,14 @@ function appendAttributes(
|
|
|
832
1068
|
const attributeName = name === 'className' ? 'class' : name
|
|
833
1069
|
const propertyAttribute = isPropertyAttribute(name)
|
|
834
1070
|
if (ts.isJsxExpression(initializer) && initializer.expression) {
|
|
835
|
-
statements.push(callStatement(propertyAttribute ? 'bindProperty' : 'bindAttribute', [
|
|
1071
|
+
statements.push(callStatement(state, propertyAttribute ? 'bindProperty' : 'bindAttribute', [
|
|
836
1072
|
element,
|
|
837
1073
|
ts.factory.createStringLiteral(propertyAttribute ? name : attributeName),
|
|
838
1074
|
createGetter(initializer.expression)
|
|
839
1075
|
], attribute))
|
|
840
1076
|
}
|
|
841
1077
|
}
|
|
842
|
-
if (staticProps.length) statements.splice(1, 0, callStatement('setStaticProps', [
|
|
1078
|
+
if (staticProps.length) statements.splice(1, 0, callStatement(state, 'setStaticProps', [
|
|
843
1079
|
element,
|
|
844
1080
|
ts.factory.createObjectLiteralExpression(staticProps, true)
|
|
845
1081
|
], attributes))
|
|
@@ -856,6 +1092,7 @@ function isPropertyAttribute(name: string): boolean {
|
|
|
856
1092
|
}
|
|
857
1093
|
|
|
858
1094
|
function appendChildren(
|
|
1095
|
+
state: CompileState,
|
|
859
1096
|
statements: ts.Statement[],
|
|
860
1097
|
element: ts.Identifier,
|
|
861
1098
|
children: readonly ts.JsxChild[],
|
|
@@ -865,9 +1102,9 @@ function appendChildren(
|
|
|
865
1102
|
if (ts.isJsxText(child)) {
|
|
866
1103
|
const text = child.text.replace(/\s+/g, ' ').trimStart()
|
|
867
1104
|
if (text.trim()) {
|
|
868
|
-
statements.push(callStatement('insertBefore', [
|
|
1105
|
+
statements.push(callStatement(state, 'insertBefore', [
|
|
869
1106
|
element,
|
|
870
|
-
ts.factory.createCallExpression(helperRef('createText'), undefined, [
|
|
1107
|
+
ts.factory.createCallExpression(helperRef(state, 'createText'), undefined, [
|
|
871
1108
|
ts.factory.createStringLiteral(text)
|
|
872
1109
|
]),
|
|
873
1110
|
anchor
|
|
@@ -877,9 +1114,9 @@ function appendChildren(
|
|
|
877
1114
|
}
|
|
878
1115
|
|
|
879
1116
|
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
880
|
-
statements.push(callStatement('insertBefore', [
|
|
1117
|
+
statements.push(callStatement(state, 'insertBefore', [
|
|
881
1118
|
element,
|
|
882
|
-
transformJsxExpression(child),
|
|
1119
|
+
transformJsxExpression(state, child),
|
|
883
1120
|
anchor
|
|
884
1121
|
], child))
|
|
885
1122
|
continue
|
|
@@ -888,30 +1125,31 @@ function appendChildren(
|
|
|
888
1125
|
if (child.kind === ts.SyntaxKind.JsxExpression) {
|
|
889
1126
|
const expression = (child as ts.JsxExpression).expression
|
|
890
1127
|
if (!expression) continue
|
|
891
|
-
const list = transformListExpression(element, expression, anchor)
|
|
1128
|
+
const list = transformListExpression(state, element, expression, anchor)
|
|
892
1129
|
if (list) {
|
|
893
|
-
statements.push(callStatement('insertList', list, child))
|
|
1130
|
+
statements.push(callStatement(state, 'insertList', list, child))
|
|
894
1131
|
continue
|
|
895
1132
|
}
|
|
896
|
-
const dynamic = transformDynamicExpression(expression)
|
|
1133
|
+
const dynamic = transformDynamicExpression(state, expression)
|
|
897
1134
|
if (dynamic) {
|
|
898
|
-
statements.push(callStatement('insertDynamic', [element, anchor, dynamic], child))
|
|
1135
|
+
statements.push(callStatement(state, 'insertDynamic', [element, anchor, dynamic], child))
|
|
899
1136
|
continue
|
|
900
1137
|
}
|
|
901
1138
|
if (!containsJsx(expression) && !ts.isIdentifier(expression)) {
|
|
902
|
-
const textId = nextIdentifier('_text')
|
|
903
|
-
statements.push(createConstStatement(textId, ts.factory.createCallExpression(helperRef('createText'), undefined, [ts.factory.createStringLiteral('')]), child))
|
|
904
|
-
statements.push(callStatement('insertBefore', [element, textId, anchor], child))
|
|
905
|
-
statements.push(callStatement('bindText', [textId, createGetter(expression)], child))
|
|
1139
|
+
const textId = nextIdentifier(state, '_text')
|
|
1140
|
+
statements.push(createConstStatement(state, textId, ts.factory.createCallExpression(helperRef(state, 'createText'), undefined, [ts.factory.createStringLiteral('')]), child))
|
|
1141
|
+
statements.push(callStatement(state, 'insertBefore', [element, textId, anchor], child))
|
|
1142
|
+
statements.push(callStatement(state, 'bindText', [textId, createGetter(expression)], child))
|
|
906
1143
|
continue
|
|
907
1144
|
}
|
|
908
|
-
const value = transformEmbeddedExpression(expression)
|
|
909
|
-
statements.push(callStatement('insertDynamicValue', [element, anchor, createGetter(value)], child))
|
|
1145
|
+
const value = transformEmbeddedExpression(state, expression)
|
|
1146
|
+
statements.push(callStatement(state, 'insertDynamicValue', [element, anchor, createGetter(value)], child))
|
|
910
1147
|
}
|
|
911
1148
|
}
|
|
912
1149
|
}
|
|
913
1150
|
|
|
914
1151
|
function createComponentProps(
|
|
1152
|
+
state: CompileState,
|
|
915
1153
|
attributes: ts.JsxAttributes,
|
|
916
1154
|
children: readonly ts.JsxChild[]
|
|
917
1155
|
): ts.ObjectLiteralExpression {
|
|
@@ -931,11 +1169,11 @@ function createComponentProps(
|
|
|
931
1169
|
} else if (ts.isStringLiteral(initializer)) {
|
|
932
1170
|
properties.push(ts.factory.createPropertyAssignment(name, ts.factory.createStringLiteral(initializer.text)))
|
|
933
1171
|
} else if (ts.isJsxExpression(initializer) && initializer.expression) {
|
|
934
|
-
properties.push(createGetterProperty(name, transformEmbeddedExpression(initializer.expression)))
|
|
1172
|
+
properties.push(createGetterProperty(name, transformEmbeddedExpression(state, initializer.expression)))
|
|
935
1173
|
}
|
|
936
1174
|
}
|
|
937
1175
|
|
|
938
|
-
const childExpressions = children.flatMap(childToComponentExpression)
|
|
1176
|
+
const childExpressions = children.flatMap(child => childToComponentExpression(state, child))
|
|
939
1177
|
if (childExpressions.length === 1) {
|
|
940
1178
|
properties.push(createGetterProperty('children', childExpressions[0]))
|
|
941
1179
|
} else if (childExpressions.length > 1) {
|
|
@@ -955,22 +1193,22 @@ function createSourceLocation(node: ts.Node): ts.ObjectLiteralExpression {
|
|
|
955
1193
|
], true)
|
|
956
1194
|
}
|
|
957
1195
|
|
|
958
|
-
function transformDynamicExpression(expression: ts.Expression): ts.ArrowFunction | null {
|
|
1196
|
+
function transformDynamicExpression(state: CompileState, expression: ts.Expression): ts.ArrowFunction | null {
|
|
959
1197
|
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
960
1198
|
const right = unwrapExpression(expression.right)
|
|
961
1199
|
if (!isJsxExpression(right)) return null
|
|
962
1200
|
return createGetter(ts.factory.createConditionalExpression(
|
|
963
1201
|
expression.left,
|
|
964
1202
|
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
965
|
-
transformJsxExpression(right),
|
|
1203
|
+
transformJsxExpression(state, right),
|
|
966
1204
|
ts.factory.createToken(ts.SyntaxKind.ColonToken),
|
|
967
1205
|
ts.factory.createNull()
|
|
968
1206
|
))
|
|
969
1207
|
}
|
|
970
1208
|
|
|
971
1209
|
if (ts.isConditionalExpression(expression)) {
|
|
972
|
-
const whenTrue = transformDynamicBranch(expression.whenTrue)
|
|
973
|
-
const whenFalse = transformDynamicBranch(expression.whenFalse)
|
|
1210
|
+
const whenTrue = transformDynamicBranch(state, expression.whenTrue)
|
|
1211
|
+
const whenFalse = transformDynamicBranch(state, expression.whenFalse)
|
|
974
1212
|
if (!whenTrue && !whenFalse) return null
|
|
975
1213
|
return createGetter(ts.factory.createConditionalExpression(
|
|
976
1214
|
expression.condition,
|
|
@@ -984,14 +1222,15 @@ function transformDynamicExpression(expression: ts.Expression): ts.ArrowFunction
|
|
|
984
1222
|
return null
|
|
985
1223
|
}
|
|
986
1224
|
|
|
987
|
-
function transformDynamicBranch(expression: ts.Expression): ts.Expression | null {
|
|
1225
|
+
function transformDynamicBranch(state: CompileState, expression: ts.Expression): ts.Expression | null {
|
|
988
1226
|
const branch = unwrapExpression(expression)
|
|
989
|
-
if (isJsxExpression(branch)) return transformJsxExpression(branch)
|
|
1227
|
+
if (isJsxExpression(branch)) return transformJsxExpression(state, branch)
|
|
990
1228
|
if (branch.kind === ts.SyntaxKind.NullKeyword || branch.kind === ts.SyntaxKind.FalseKeyword) return branch
|
|
991
1229
|
return null
|
|
992
1230
|
}
|
|
993
1231
|
|
|
994
1232
|
function transformListExpression(
|
|
1233
|
+
state: CompileState,
|
|
995
1234
|
parent: ts.Identifier,
|
|
996
1235
|
expression: ts.Expression,
|
|
997
1236
|
anchor: ts.Expression
|
|
@@ -1005,7 +1244,7 @@ function transformListExpression(
|
|
|
1005
1244
|
if (!isJsxExpression(body) || ts.isJsxFragment(body)) return null
|
|
1006
1245
|
|
|
1007
1246
|
const key = findKeyExpression(body)
|
|
1008
|
-
const renderItem = transformListCallback(callback, transformJsxExpression(body))
|
|
1247
|
+
const renderItem = transformListCallback(callback, transformJsxExpression(state, body))
|
|
1009
1248
|
const args: ts.Expression[] = [
|
|
1010
1249
|
parent,
|
|
1011
1250
|
anchor,
|
|
@@ -1073,17 +1312,17 @@ function unwrapExpression(node: ts.Expression | ts.ConciseBody): ts.Expression {
|
|
|
1073
1312
|
return ts.isParenthesizedExpression(node) ? node.expression : node as ts.Expression
|
|
1074
1313
|
}
|
|
1075
1314
|
|
|
1076
|
-
function childToComponentExpression(child: ts.JsxChild): ts.Expression[] {
|
|
1315
|
+
function childToComponentExpression(state: CompileState, child: ts.JsxChild): ts.Expression[] {
|
|
1077
1316
|
if (ts.isJsxText(child)) {
|
|
1078
1317
|
const text = child.text.replace(/\s+/g, ' ').trim()
|
|
1079
1318
|
return text ? [ts.factory.createStringLiteral(text)] : []
|
|
1080
1319
|
}
|
|
1081
1320
|
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
1082
|
-
return [transformJsxExpression(child)]
|
|
1321
|
+
return [transformJsxExpression(state, child)]
|
|
1083
1322
|
}
|
|
1084
1323
|
if (child.kind === ts.SyntaxKind.JsxExpression) {
|
|
1085
1324
|
const expression = (child as ts.JsxExpression).expression
|
|
1086
|
-
return expression ? [transformEmbeddedExpression(expression)] : []
|
|
1325
|
+
return expression ? [transformEmbeddedExpression(state, expression)] : []
|
|
1087
1326
|
}
|
|
1088
1327
|
return []
|
|
1089
1328
|
}
|
|
@@ -1115,42 +1354,42 @@ function createGetterProperty(name: string | ts.PropertyName, expression: ts.Exp
|
|
|
1115
1354
|
)
|
|
1116
1355
|
}
|
|
1117
1356
|
|
|
1118
|
-
function callStatement(name: string, args: ts.Expression[], source?: ts.Node): ts.ExpressionStatement {
|
|
1357
|
+
function callStatement(state: CompileState, name: string, args: ts.Expression[], source?: ts.Node): ts.ExpressionStatement {
|
|
1119
1358
|
const statement = ts.factory.createExpressionStatement(
|
|
1120
|
-
ts.factory.createCallExpression(helperRef(name), undefined, args)
|
|
1359
|
+
ts.factory.createCallExpression(helperRef(state, name), undefined, args)
|
|
1121
1360
|
)
|
|
1122
|
-
return source ? tagStatement(statement, source) : statement
|
|
1361
|
+
return source ? tagStatement(state, statement, source) : statement
|
|
1123
1362
|
}
|
|
1124
1363
|
|
|
1125
|
-
function createConstStatement(name: ts.Identifier, initializer: ts.Expression, source?: ts.Node): ts.VariableStatement {
|
|
1364
|
+
function createConstStatement(state: CompileState, name: ts.Identifier, initializer: ts.Expression, source?: ts.Node): ts.VariableStatement {
|
|
1126
1365
|
const statement = ts.factory.createVariableStatement(
|
|
1127
1366
|
undefined,
|
|
1128
1367
|
ts.factory.createVariableDeclarationList([
|
|
1129
1368
|
ts.factory.createVariableDeclaration(name, undefined, undefined, initializer)
|
|
1130
1369
|
], ts.NodeFlags.Const)
|
|
1131
1370
|
)
|
|
1132
|
-
return source ? tagStatement(statement, source) : statement
|
|
1371
|
+
return source ? tagStatement(state, statement, source) : statement
|
|
1133
1372
|
}
|
|
1134
1373
|
|
|
1135
1374
|
/** Record where an emitted statement originated from, for source map generation. */
|
|
1136
|
-
function tagStatement<T extends ts.Statement>(statement: T, source: ts.Node): T {
|
|
1137
|
-
const position = positionOfNode(source)
|
|
1138
|
-
if (position) statementSources.set(statement, position)
|
|
1375
|
+
function tagStatement<T extends ts.Statement>(state: CompileState, statement: T, source: ts.Node): T {
|
|
1376
|
+
const position = positionOfNode(state, source)
|
|
1377
|
+
if (position) state.statementSources.set(statement, position)
|
|
1139
1378
|
return statement
|
|
1140
1379
|
}
|
|
1141
1380
|
|
|
1142
|
-
function positionOfNode(node: ts.Node): SourcePosition | null {
|
|
1381
|
+
function positionOfNode(state: CompileState, node: ts.Node): SourcePosition | null {
|
|
1143
1382
|
// ts.transform 产生的节点副本可能丢失 sourceFile 引用,回退到当前编译的源文件。
|
|
1144
|
-
const sourceFile = node.getSourceFile() ??
|
|
1383
|
+
const sourceFile = node.getSourceFile() ?? state.sourceFile
|
|
1145
1384
|
if (!sourceFile || node.pos < 0) return null
|
|
1146
1385
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
1147
1386
|
return { line, column: character }
|
|
1148
1387
|
}
|
|
1149
1388
|
|
|
1150
|
-
function nextIdentifier(prefix: string): ts.Identifier {
|
|
1389
|
+
function nextIdentifier(state: CompileState, prefix: string): ts.Identifier {
|
|
1151
1390
|
let name: string
|
|
1152
1391
|
do {
|
|
1153
|
-
name = `${prefix}${generatedId++}`
|
|
1154
|
-
} while (takenNames.has(name))
|
|
1392
|
+
name = `${prefix}${state.generatedId++}`
|
|
1393
|
+
} while (state.takenNames.has(name))
|
|
1155
1394
|
return ts.factory.createIdentifier(name)
|
|
1156
1395
|
}
|