@vobs/compiler 1.1.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/src/compile.ts CHANGED
@@ -33,6 +33,10 @@ interface CompileState {
33
33
  templates: Map<string, ts.Identifier>
34
34
  /** 是否为组件调用生成源码位置(生产构建传 false 剔除,减小产物体积)。 */
35
35
  sourceLocation: boolean
36
+ /** 从 @vobs/reactivity / @vobs/vobs 导入的 `state` 别名(含 as 别名),用于 debugName 自动推断。 */
37
+ stateAliases: ReadonlySet<string>
38
+ /** 非 import 的本地声明绑定名:`state` 被本地声明遮蔽时禁用 debugName 推断。 */
39
+ localBindings: ReadonlySet<string>
36
40
  /** 编译器自身产出的诊断(如不支持的 JSX 形态),与 TypeScript 解析诊断合并返回。 */
37
41
  diagnostics: CompilerDiagnostic[]
38
42
  }
@@ -95,6 +99,8 @@ export function compileWithSourceMap(code: string, options: CompileOptions = {})
95
99
  helperAliases: new Map(),
96
100
  templates: new Map(),
97
101
  sourceLocation: options.sourceLocation ?? true,
102
+ stateAliases: collectStateAliases(sourceFile),
103
+ localBindings: collectLocallyDeclaredNames(sourceFile),
98
104
  diagnostics: []
99
105
  }
100
106
  const cleanFilename = filename.split(/[?#]/u, 1)[0] || filename
@@ -126,9 +132,12 @@ export function compileWithSourceMap(code: string, options: CompileOptions = {})
126
132
  const statements = sourceFile.statements.map(statement =>
127
133
  ts.isImportDeclaration(statement) ? rebuildImport(state, statement) : transformStatement(state, statement)
128
134
  )
135
+ // 模板声明必须先于 runtime import 生成:声明里的 createTemplate 依赖
136
+ // helperRef 注册别名,import 需要在别名全部就绪后再构建。
137
+ const templateDeclarations = createTemplateDeclarations(state)
129
138
  const resultFile = ts.factory.updateSourceFile(sourceFile, [
130
139
  ...createRuntimeImports(state),
131
- ...createTemplateDeclarations(state),
140
+ ...templateDeclarations,
132
141
  ...statements
133
142
  ])
134
143
 
@@ -384,6 +393,36 @@ function collectDeclaredNames(sourceFile: ts.SourceFile): Set<string> {
384
393
  return names
385
394
  }
386
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
+
387
426
  /** 标识符是否为某个声明的绑定名(import、变量、函数、参数、类成员等)。 */
388
427
  function isBindingName(node: ts.Identifier): boolean {
389
428
  const parent = node.parent
@@ -479,22 +518,28 @@ function transformStatement(state: CompileState, node: ts.Statement): ts.Stateme
479
518
  }
480
519
 
481
520
  function transformVariableStatement(state: CompileState, node: ts.VariableStatement): ts.VariableStatement {
521
+ let changed = false
482
522
  const declarations = node.declarationList.declarations.map(declaration => {
483
523
  const initializer = declaration.initializer
484
- if (!initializer || !containsJsx(initializer)) return declaration
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
485
531
 
532
+ changed = true
486
533
  return ts.factory.updateVariableDeclaration(
487
534
  declaration,
488
535
  declaration.name,
489
536
  declaration.exclamationToken,
490
537
  declaration.type,
491
- transformEmbeddedExpression(state, initializer)
538
+ nextInitializer
492
539
  )
493
540
  })
494
541
 
495
- if (declarations.every((declaration, index) => declaration === node.declarationList.declarations[index])) {
496
- return node
497
- }
542
+ if (!changed) return node
498
543
 
499
544
  return tagStatement(state, ts.factory.updateVariableStatement(
500
545
  node,
@@ -503,6 +548,36 @@ function transformVariableStatement(state: CompileState, node: ts.VariableStatem
503
548
  ), node)
504
549
  }
505
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
+
506
581
  function containsJsx(expression: ts.Expression): boolean {
507
582
  let found = false
508
583
  const visit = (node: ts.Node): void => {