sinwan-compiler 1.0.0 → 1.1.1

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 CHANGED
@@ -19,7 +19,7 @@ bun add -d sinwan-compiler
19
19
 
20
20
  App templates already depend on the Bun or Vite plugin. You only need this package if you call the compiler yourself.
21
21
 
22
- The plugins declare `sinwan-compiler` as `>=0.2.5 <1.0.0`. After you publish a new 0.x compiler, do **not** bump the plugins. Apps pick it up with a fresh install or `bun update sinwan-compiler`. Republish a plugin only if its own API changed.
22
+ The plugins depend on `sinwan-compiler` `>=1.0.0 <2.0.0`. A new 1.x compiler is picked up with `bun update sinwan-compiler`. Publish a new plugin when its own API changes, or when the compiler reaches 2.0.
23
23
 
24
24
  ## Production analysis
25
25
 
@@ -1156,6 +1156,38 @@ function unwrapFunctionPath(fnPath) {
1156
1156
  function ccCall(arg, ccName) {
1157
1157
  return callExpression(identifier(ccName), [arg]);
1158
1158
  }
1159
+ function withTypes(fn, from) {
1160
+ fn.typeParameters = from.typeParameters;
1161
+ fn.returnType = from.returnType;
1162
+ return fn;
1163
+ }
1164
+ function isReadBeforeDeclaration(path, name) {
1165
+ const start = path.node.start ?? 0;
1166
+ return (path.parentPath.scope.getBinding(name)?.referencePaths ?? []).some((ref) => (ref.node.start ?? 0) < start && !ref.getFunctionParent() && ref.parent?.type !== "ExportSpecifier" && !ref.findParent((parent) => parent.isTSType()));
1167
+ }
1168
+ function collectSpecifierExports(ast) {
1169
+ const exports = new Map;
1170
+ traverse(ast, {
1171
+ ExportNamedDeclaration(path) {
1172
+ const node = path.node;
1173
+ if (node.declaration || node.source || node.exportKind === "type")
1174
+ return;
1175
+ for (const spec of node.specifiers ?? []) {
1176
+ if (!isExportSpecifier(spec) || spec.exportKind === "type")
1177
+ continue;
1178
+ if (!isIdentifier(spec.local) || !isIdentifier(spec.exported))
1179
+ continue;
1180
+ if (isComponentName(spec.exported.name)) {
1181
+ exports.set(spec.local.name, spec.exported.name);
1182
+ }
1183
+ }
1184
+ }
1185
+ });
1186
+ return exports;
1187
+ }
1188
+ function isTopLevel(path) {
1189
+ return path.parent?.type === "Program";
1190
+ }
1159
1191
  function autoWrapComponents2(ast, filename) {
1160
1192
  const { aliases, alreadyImported } = collectCcAliases(ast);
1161
1193
  const ccName = aliases.values().next().value;
@@ -1170,6 +1202,24 @@ function autoWrapComponents2(ast, filename) {
1170
1202
  return false;
1171
1203
  return true;
1172
1204
  };
1205
+ const wrapDeclarators = (declarators, componentName) => {
1206
+ for (const dPath of declarators) {
1207
+ const id = dPath.node.id;
1208
+ if (!isIdentifier(id))
1209
+ continue;
1210
+ const name = componentName(id.name);
1211
+ if (name === undefined || !dPath.node.init)
1212
+ continue;
1213
+ const initPath = unwrapFunctionPath(dPath.get("init"));
1214
+ if (!isFunction(initPath.node))
1215
+ continue;
1216
+ if (!wrapIfCandidate(initPath, name))
1217
+ continue;
1218
+ dPath.node.init = ccCall(initPath.node, ccName ?? "cc");
1219
+ changed = true;
1220
+ }
1221
+ };
1222
+ const specifierExports = collectSpecifierExports(ast);
1173
1223
  traverse(ast, {
1174
1224
  ExportNamedDeclaration(path) {
1175
1225
  const decl = path.node.declaration;
@@ -1180,7 +1230,7 @@ function autoWrapComponents2(ast, filename) {
1180
1230
  const fnPath = path.get("declaration");
1181
1231
  if (!wrapIfCandidate(fnPath, name))
1182
1232
  return;
1183
- const fnExpr = functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async);
1233
+ const fnExpr = withTypes(functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async), decl);
1184
1234
  const varDecl = variableDeclaration("const", [
1185
1235
  variableDeclarator(identifier(name), ccCall(fnExpr, ccName ?? "cc"))
1186
1236
  ]);
@@ -1189,27 +1239,30 @@ function autoWrapComponents2(ast, filename) {
1189
1239
  return;
1190
1240
  }
1191
1241
  if (isVariableDeclaration(decl)) {
1192
- for (const dPath of path.get("declaration.declarations")) {
1193
- const id = dPath.node.id;
1194
- if (!isIdentifier(id) || !isComponentName(id.name))
1195
- continue;
1196
- const init = dPath.node.init ? unwrapParens(dPath.node.init) : dPath.node.init;
1197
- if (!init)
1198
- continue;
1199
- let initPath = unwrapFunctionPath(dPath.get("init"));
1200
- if (isCallExpression(initPath.node) && isIdentifier(initPath.node.callee) && aliases.has(initPath.node.callee.name)) {
1201
- continue;
1202
- }
1203
- if (!isFunction(initPath.node))
1204
- continue;
1205
- if (!wrapIfCandidate(initPath, id.name))
1206
- continue;
1207
- dPath.node.init = ccCall(initPath.node, ccName ?? "cc");
1208
- changed = true;
1209
- }
1242
+ wrapDeclarators(path.get("declaration.declarations"), (name) => isComponentName(name) ? name : undefined);
1210
1243
  return;
1211
1244
  }
1212
1245
  },
1246
+ FunctionDeclaration(path) {
1247
+ const decl = path.node;
1248
+ if (!isTopLevel(path) || !isIdentifier(decl.id))
1249
+ return;
1250
+ const exported = specifierExports.get(decl.id.name);
1251
+ if (exported === undefined || !wrapIfCandidate(path, exported))
1252
+ return;
1253
+ if (isReadBeforeDeclaration(path, decl.id.name))
1254
+ return;
1255
+ const fnExpr = withTypes(functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async), decl);
1256
+ path.replaceWith(variableDeclaration("const", [
1257
+ variableDeclarator(identifier(decl.id.name), ccCall(fnExpr, ccName ?? "cc"))
1258
+ ]));
1259
+ changed = true;
1260
+ },
1261
+ VariableDeclaration(path) {
1262
+ if (!isTopLevel(path))
1263
+ return;
1264
+ wrapDeclarators(path.get("declarations"), (name) => specifierExports.get(name));
1265
+ },
1213
1266
  ExportDefaultDeclaration(path) {
1214
1267
  const decl = isNode(path.node.declaration) ? unwrapParens(path.node.declaration) : path.node.declaration;
1215
1268
  if (isFunctionDeclaration(decl) && isIdentifier(decl.id)) {
@@ -1217,7 +1270,7 @@ function autoWrapComponents2(ast, filename) {
1217
1270
  const fnPath = path.get("declaration");
1218
1271
  if (!wrapIfCandidate(fnPath, name))
1219
1272
  return;
1220
- const fnDecl = functionDeclaration(decl.id, decl.params, decl.body, decl.generator, decl.async);
1273
+ const fnDecl = withTypes(functionDeclaration(decl.id, decl.params, decl.body, decl.generator, decl.async), decl);
1221
1274
  const exportDefault = exportDefaultDeclaration(ccCall(identifier(name), ccName ?? "cc"));
1222
1275
  path.replaceWithMultiple([fnDecl, exportDefault]);
1223
1276
  changed = true;
@@ -1227,7 +1280,7 @@ function autoWrapComponents2(ast, filename) {
1227
1280
  const fnPath = path.get("declaration");
1228
1281
  if (!wrapIfCandidate(fnPath, null))
1229
1282
  return;
1230
- const fnExpr = functionExpression(null, decl.params, decl.body, decl.generator, decl.async);
1283
+ const fnExpr = withTypes(functionExpression(null, decl.params, decl.body, decl.generator, decl.async), decl);
1231
1284
  path.node.declaration = ccCall(fnExpr, ccName ?? "cc");
1232
1285
  changed = true;
1233
1286
  return;
@@ -2236,7 +2289,7 @@ class AnalyzerCache2 {
2236
2289
  if (!fs.existsSync(dir)) {
2237
2290
  fs.mkdirSync(dir, { recursive: true });
2238
2291
  }
2239
- fs.writeFileSync(this.cachePath, JSON.stringify(data, null, 2));
2292
+ fs.writeFileSync(this.cachePath, JSON.stringify(data));
2240
2293
  }
2241
2294
  restore() {
2242
2295
  if (!this.cachePath || !fs.existsSync(this.cachePath))
@@ -2492,7 +2545,32 @@ function trackReactiveImports2(ast) {
2492
2545
  });
2493
2546
  return names;
2494
2547
  }
2548
+ function fileStamp(file) {
2549
+ return fs2.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
2550
+ }
2551
+ function freshScan(cache, file) {
2552
+ const entry = cache.get(file);
2553
+ if (!entry)
2554
+ return;
2555
+ for (const [stamped, mtime] of entry.stamps) {
2556
+ if (fileStamp(stamped) !== mtime)
2557
+ return;
2558
+ }
2559
+ return entry;
2560
+ }
2561
+ function mergeStamps(into, from) {
2562
+ for (const [file, mtime] of from)
2563
+ into.set(file, mtime);
2564
+ }
2495
2565
  var pureImportCache = new Map;
2566
+ function pureExportsOf(filePath, resolveImport, depth = 0) {
2567
+ const cached = freshScan(pureImportCache, filePath);
2568
+ if (cached)
2569
+ return cached;
2570
+ const scan = scanPureExports(filePath, resolveImport, depth);
2571
+ pureImportCache.set(filePath, scan);
2572
+ return scan;
2573
+ }
2496
2574
  var PURE_EXTENSIONS = [
2497
2575
  ".ts",
2498
2576
  ".tsx",
@@ -2613,13 +2691,14 @@ function resolveAnyImport(source, fromFile, resolveImport) {
2613
2691
  }
2614
2692
  function scanPureExports(filePath, resolveImport, depth = 0) {
2615
2693
  const pure = new Map;
2694
+ const scan = { value: pure, stamps: new Map([[filePath, fileStamp(filePath)]]) };
2616
2695
  if (depth > 10)
2617
- return pure;
2696
+ return scan;
2618
2697
  let code;
2619
2698
  try {
2620
2699
  code = fs2.readFileSync(filePath, "utf-8");
2621
2700
  } catch {
2622
- return pure;
2701
+ return scan;
2623
2702
  }
2624
2703
  let ast;
2625
2704
  try {
@@ -2629,7 +2708,7 @@ function scanPureExports(filePath, resolveImport, depth = 0) {
2629
2708
  sourceFilename: filePath
2630
2709
  });
2631
2710
  } catch {
2632
- return pure;
2711
+ return scan;
2633
2712
  }
2634
2713
  const hasPureJSDoc = (node) => node?.leadingComments?.some((c) => c.type === "CommentBlock" && c.value.includes("@sinwan-pure")) ?? false;
2635
2714
  traverse(ast, {
@@ -2657,11 +2736,9 @@ function scanPureExports(filePath, resolveImport, depth = 0) {
2657
2736
  if (source && p.node.specifiers) {
2658
2737
  const resolved = resolveAnyImport(source, filePath, resolveImport);
2659
2738
  if (resolved) {
2660
- let reExportPure = pureImportCache.get(resolved);
2661
- if (!reExportPure) {
2662
- reExportPure = scanPureExports(resolved, resolveImport, depth + 1);
2663
- pureImportCache.set(resolved, reExportPure);
2664
- }
2739
+ const nested = pureExportsOf(resolved, resolveImport, depth + 1);
2740
+ mergeStamps(scan.stamps, nested.stamps);
2741
+ const reExportPure = nested.value;
2665
2742
  for (const spec of p.node.specifiers ?? []) {
2666
2743
  if (!isExportSpecifier(spec))
2667
2744
  continue;
@@ -2692,11 +2769,9 @@ function scanPureExports(filePath, resolveImport, depth = 0) {
2692
2769
  const resolved = resolveAnyImport(source, filePath, resolveImport);
2693
2770
  if (!resolved)
2694
2771
  return;
2695
- let reExportPure = pureImportCache.get(resolved);
2696
- if (!reExportPure) {
2697
- reExportPure = scanPureExports(resolved, resolveImport, depth + 1);
2698
- pureImportCache.set(resolved, reExportPure);
2699
- }
2772
+ const nested = pureExportsOf(resolved, resolveImport, depth + 1);
2773
+ mergeStamps(scan.stamps, nested.stamps);
2774
+ const reExportPure = nested.value;
2700
2775
  for (const [name, kind] of reExportPure) {
2701
2776
  if (name !== "default") {
2702
2777
  pure.set(name, kind);
@@ -2704,7 +2779,7 @@ function scanPureExports(filePath, resolveImport, depth = 0) {
2704
2779
  }
2705
2780
  }
2706
2781
  });
2707
- return pure;
2782
+ return scan;
2708
2783
  }
2709
2784
  function collectPureImports(ast, filename, resolveImport) {
2710
2785
  const result = {
@@ -2725,11 +2800,7 @@ function collectPureImports(ast, filename, resolveImport) {
2725
2800
  const resolved = resolveAnyImport(source, absoluteFilename, resolveImport);
2726
2801
  if (!resolved)
2727
2802
  return;
2728
- let filePure = pureImportCache.get(resolved);
2729
- if (!filePure) {
2730
- filePure = scanPureExports(resolved, resolveImport);
2731
- pureImportCache.set(resolved, filePure);
2732
- }
2803
+ const filePure = pureExportsOf(resolved, resolveImport).value;
2733
2804
  for (const spec of p.node.specifiers ?? []) {
2734
2805
  if (isImportSpecifier(spec)) {
2735
2806
  const imported = importedBindingName(spec.imported);
@@ -3684,11 +3755,16 @@ function hasSnapshotPragma(node) {
3684
3755
  function isTypePosition(ref) {
3685
3756
  return ref.findParent((parent) => parent.isTSType()) != null;
3686
3757
  }
3758
+ function hasPureJSDoc(node) {
3759
+ return node?.leadingComments?.some((comment) => comment.type === "CommentBlock" && comment.value.includes("@sinwan-pure")) ?? false;
3760
+ }
3687
3761
  function canRewriteDerivedReference(ref) {
3688
3762
  if (!ref.isIdentifier() || !ref.isReferencedIdentifier())
3689
3763
  return false;
3690
3764
  if (isTypePosition(ref))
3691
3765
  return true;
3766
+ if (isPureArrayReadReceiver(ref))
3767
+ return true;
3692
3768
  let target = ref;
3693
3769
  while ((target.parentPath?.isMemberExpression() || target.parentPath?.isOptionalMemberExpression()) && target.key === "object" || (target.parentPath?.isTSNonNullExpression() || target.parentPath?.isTSAsExpression() || target.parentPath?.isTSTypeAssertion() || target.parentPath?.isTSSatisfiesExpression() || target.parentPath?.isParenthesizedExpression()) && target.key === "expression") {
3694
3770
  target = target.parentPath;
@@ -3696,12 +3772,165 @@ function canRewriteDerivedReference(ref) {
3696
3772
  const parent = target.parentPath;
3697
3773
  return !(parent?.isAssignmentExpression() && target.key === "left" || parent?.isUpdateExpression() || parent?.isUnaryExpression({ operator: "delete" }) || (parent?.isForInStatement() || parent?.isForOfStatement()) && target.key === "left" || target !== ref && (parent?.isCallExpression() || parent?.isOptionalCallExpression()) && target.key === "callee");
3698
3774
  }
3699
- function isPureDerivedCall(path, names, pureImports) {
3700
- if (!path.isCallExpression() && !path.isOptionalCallExpression())
3775
+ var ARRAY_RESULT_READS = new Set(["map", "filter", "flatMap", "flat", "slice", "concat"]);
3776
+ var CALLBACK_ARRAY_READS = new Set([
3777
+ "map",
3778
+ "filter",
3779
+ "flatMap",
3780
+ "find",
3781
+ "findIndex",
3782
+ "some",
3783
+ "every",
3784
+ "reduce"
3785
+ ]);
3786
+ var PURE_ARRAY_READS = new Set([
3787
+ ...ARRAY_RESULT_READS,
3788
+ ...CALLBACK_ARRAY_READS,
3789
+ "join",
3790
+ "at",
3791
+ "includes",
3792
+ "indexOf",
3793
+ "entries",
3794
+ "keys",
3795
+ "values"
3796
+ ]);
3797
+ var DATE_READS = new Set([
3798
+ "getFullYear",
3799
+ "getMonth",
3800
+ "getDate",
3801
+ "getDay",
3802
+ "getTime",
3803
+ "getHours",
3804
+ "getMinutes",
3805
+ "getSeconds",
3806
+ "getMilliseconds",
3807
+ "valueOf"
3808
+ ]);
3809
+ var PURE_METHOD_READS = new Set([...PURE_ARRAY_READS, ...DATE_READS]);
3810
+ function builtinKindOfType(node) {
3811
+ const typeNode = unwrapTypeNode(node);
3812
+ if (typeNode?.type === "TSArrayType")
3813
+ return "array";
3814
+ if (typeNode?.type !== "TSTypeReference")
3815
+ return null;
3816
+ const name = typeNode.typeName;
3817
+ if (!isIdentifier(name))
3818
+ return null;
3819
+ if (name.name === "Array" || name.name === "ReadonlyArray")
3820
+ return "array";
3821
+ return name.name === "Date" ? "date" : null;
3822
+ }
3823
+ function isGlobalArrayFrom(callee, scope) {
3824
+ return isMemberExpression(callee) && !callee.computed && isIdentifier(callee.object, { name: "Array" }) && isIdentifier(callee.property, { name: "from" }) && !scope.getBinding("Array");
3825
+ }
3826
+ function builtinKindOf(node, scope, seen = new Set) {
3827
+ const expr = unwrapParens(node);
3828
+ if (expr.type === "ArrayExpression")
3829
+ return "array";
3830
+ if (isCallExpression(expr)) {
3831
+ const callee = unwrapParens(expr.callee);
3832
+ if (isGlobalArrayFrom(callee, scope))
3833
+ return "array";
3834
+ if (isMemberExpression(callee)) {
3835
+ return !callee.computed && isIdentifier(callee.property) && ARRAY_RESULT_READS.has(callee.property.name) && builtinKindOf(callee.object, scope, seen) === "array" ? "array" : null;
3836
+ }
3837
+ const fn = isIdentifier(callee) ? scope.getBinding(callee.name)?.path : undefined;
3838
+ const declared = fn?.isVariableDeclarator() ? fn.node.init : fn?.node;
3839
+ return functionParamCount(declared) === null ? null : builtinKindOfType(declared?.returnType);
3840
+ }
3841
+ if (!isIdentifier(expr))
3842
+ return null;
3843
+ const binding = scope.getBinding(expr.name);
3844
+ if (!binding || binding.kind !== "const" || !binding.path.isVariableDeclarator() || seen.has(binding.path.node)) {
3845
+ return null;
3846
+ }
3847
+ seen.add(binding.path.node);
3848
+ const declarator = binding.path.node;
3849
+ return builtinKindOfType(declarator.id?.typeAnnotation) ?? (declarator.init ? builtinKindOf(declarator.init, binding.path.scope, seen) : null);
3850
+ }
3851
+ function functionParamCount(node) {
3852
+ if (!node || !isFunctionDeclaration(node) && !isFunctionExpression(node) && !isArrowFunctionExpression(node)) {
3853
+ return null;
3854
+ }
3855
+ return node.params.length;
3856
+ }
3857
+ function isPureArrayReadReceiver(ref) {
3858
+ const member = ref.parentPath;
3859
+ if (!member || !member.isMemberExpression() && !member.isOptionalMemberExpression() || ref.key !== "object" || member.node.computed || !isIdentifier(member.node.property) || !PURE_METHOD_READS.has(member.node.property.name)) {
3701
3860
  return false;
3861
+ }
3862
+ const call = member.parentPath;
3863
+ return !!call && (call.isCallExpression() || call.isOptionalCallExpression()) && member.key === "callee";
3864
+ }
3865
+ function callParts(path) {
3702
3866
  const call = unwrapCall(path.node);
3703
- const callee = unwrapParens(call.callee);
3704
- const args = call.arguments ?? [];
3867
+ return { callee: unwrapParens(call.callee), args: call.arguments ?? [] };
3868
+ }
3869
+ function isSameFilePureFunction(binding) {
3870
+ const owner = binding.path.parentPath?.node;
3871
+ if (binding.path.isFunctionDeclaration()) {
3872
+ return hasPureJSDoc(binding.path.node) || owner?.type === "ExportNamedDeclaration" && hasPureJSDoc(owner);
3873
+ }
3874
+ if (!binding.path.isVariableDeclarator())
3875
+ return false;
3876
+ const declarator = binding.path.node;
3877
+ const exported = binding.path.parentPath?.parentPath?.node;
3878
+ return functionParamCount(declarator.init) != null && (hasPureJSDoc(declarator) || hasPureJSDoc(declarator.init) || hasPureJSDoc(owner) || exported?.type === "ExportNamedDeclaration" && hasPureJSDoc(exported));
3879
+ }
3880
+ function isPureFunctionReference(node, path, pureImports) {
3881
+ if (!isIdentifier(node))
3882
+ return false;
3883
+ const binding = path.scope.getBinding(node.name);
3884
+ if (!binding)
3885
+ return false;
3886
+ return isSameFilePureFunction(binding) || (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier()) && pureImports.direct.has(node.name);
3887
+ }
3888
+ function acceptCallback(arg, path, ctx) {
3889
+ if (isArrowFunctionExpression(arg) || isFunctionExpression(arg)) {
3890
+ ctx.callbacks.add(arg);
3891
+ return true;
3892
+ }
3893
+ return isPureFunctionReference(arg, path, ctx.pureImports);
3894
+ }
3895
+ function isBuiltinReadCall(path, callee, args, ctx) {
3896
+ if (isGlobalArrayFrom(callee, path.scope)) {
3897
+ return args.length < 2 || acceptCallback(args[1], path, ctx);
3898
+ }
3899
+ if (!isMemberExpression(callee) || callee.computed || !isIdentifier(callee.property)) {
3900
+ return false;
3901
+ }
3902
+ const method = callee.property.name;
3903
+ const kind = builtinKindOf(callee.object, path.scope);
3904
+ if (kind === "date")
3905
+ return DATE_READS.has(method);
3906
+ if (kind !== "array" || !PURE_ARRAY_READS.has(method))
3907
+ return false;
3908
+ return !CALLBACK_ARRAY_READS.has(method) || acceptCallback(args[0], path, ctx);
3909
+ }
3910
+ function inspectLocalGetterCall(path, ctx) {
3911
+ const { callee, args } = callParts(path);
3912
+ if (args.length !== 0 || !isIdentifier(callee))
3913
+ return null;
3914
+ const binding = path.scope.getBinding(callee.name);
3915
+ if (!binding?.path.isVariableDeclarator() || isSameFilePureFunction(binding)) {
3916
+ return null;
3917
+ }
3918
+ const declarator = binding.path;
3919
+ const getter = declarator.node.init;
3920
+ if (functionParamCount(getter) !== 0)
3921
+ return null;
3922
+ if (!isArrowFunctionExpression(getter) || getter.async || isBlockStatement(getter.body) || ctx.visiting.has(getter)) {
3923
+ return { safe: false, reactive: false };
3924
+ }
3925
+ ctx.visiting.add(getter);
3926
+ const result = inspectDerivedExpression(singlePath(declarator.get("init.body")), declarator.node.start ?? Infinity, ctx);
3927
+ ctx.visiting.delete(getter);
3928
+ return result;
3929
+ }
3930
+ function isPureDerivedCall(path, ctx) {
3931
+ const { callee, args } = callParts(path);
3932
+ if (isBuiltinReadCall(path, callee, args, ctx))
3933
+ return true;
3705
3934
  if (isMemberExpression(callee) || isOptionalMemberExpression(callee)) {
3706
3935
  const props = [];
3707
3936
  let node = unwrapParens(callee);
@@ -3726,10 +3955,10 @@ function isPureDerivedCall(path, names, pureImports) {
3726
3955
  if (!isIdentifier(node))
3727
3956
  return false;
3728
3957
  const root = node.name;
3729
- if (pureImports.member.has(root)) {
3958
+ if (ctx.pureImports.member.has(root)) {
3730
3959
  return true;
3731
3960
  }
3732
- const nsMembers = pureImports.namespace.get(root);
3961
+ const nsMembers = ctx.pureImports.namespace.get(root);
3733
3962
  if (nsMembers && props.some((p) => nsMembers.has(p))) {
3734
3963
  return true;
3735
3964
  }
@@ -3741,17 +3970,14 @@ function isPureDerivedCall(path, names, pureImports) {
3741
3970
  if (!binding) {
3742
3971
  return ["Boolean", "Number", "String"].includes(callee.name) && args.length === 1;
3743
3972
  }
3744
- if ((binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier()) && pureImports.direct.has(callee.name)) {
3973
+ if (isPureFunctionReference(callee, path, ctx.pureImports))
3745
3974
  return true;
3746
- }
3747
3975
  if (args.length !== 0 || !binding.path.isVariableDeclarator())
3748
3976
  return false;
3749
3977
  const { id, init } = binding.path.node;
3750
- return isArrayPattern(id) && id.elements[0] === binding.identifier && isCallExpression(init) && isIdentifier(init.callee) && names.useState.has(init.callee.name) && binding.path.scope.getBinding(init.callee.name)?.path.isImportSpecifier() === true;
3978
+ return isArrayPattern(id) && id.elements[0] === binding.identifier && isCallExpression(init) && isIdentifier(init.callee) && ctx.names.useState.has(init.callee.name) && binding.path.scope.getBinding(init.callee.name)?.path.isImportSpecifier() === true;
3751
3979
  }
3752
- function isDerivedInitializer(declaration, scope, names, derived, pureImports) {
3753
- const init = singlePath(declaration.get("init"));
3754
- init.assertExpression();
3980
+ function inspectDerivedExpression(root, before, ctx) {
3755
3981
  let safe = true;
3756
3982
  let reactive = false;
3757
3983
  const inspect = (path) => {
@@ -3762,29 +3988,49 @@ function isDerivedInitializer(declaration, scope, names, derived, pureImports) {
3762
3988
  const node = path.node;
3763
3989
  if (path.isReferencedIdentifier()) {
3764
3990
  const binding = path.scope.getBinding(path.node.name);
3765
- if (binding && (!binding.constant || binding.kind === "let" || binding.kind === "var" || binding.path.isVariableDeclarator() && (binding.path.node.start ?? -1) >= (declaration.node.start ?? Infinity))) {
3991
+ if (binding && (!binding.constant || binding.kind === "let" || binding.kind === "var" || binding.path.isVariableDeclarator() && (binding.path.node.start ?? -1) >= before)) {
3766
3992
  safe = false;
3767
3993
  return;
3768
3994
  }
3769
- if (binding && derived.has(binding))
3995
+ if (binding && ctx.derived.has(binding))
3770
3996
  reactive = true;
3771
- if (isReactiveRead(node, scope))
3997
+ if (isReactiveRead(node, ctx.scope))
3772
3998
  reactive = true;
3773
3999
  } else if (path.isMemberExpression() || path.isOptionalMemberExpression()) {
3774
- if (isReactiveRead(node, scope))
4000
+ if (isReactiveRead(node, ctx.scope))
3775
4001
  reactive = true;
3776
4002
  } else if (path.isCallExpression() || path.isOptionalCallExpression()) {
3777
- if (!isPureDerivedCall(path, names, pureImports))
4003
+ const getter = inspectLocalGetterCall(path, ctx);
4004
+ if (getter) {
4005
+ if (!getter.safe)
4006
+ safe = false;
4007
+ if (getter.reactive)
4008
+ reactive = true;
4009
+ } else if (!isPureDerivedCall(path, ctx)) {
3778
4010
  safe = false;
3779
- if (isGetterCall(path.node, scope))
4011
+ }
4012
+ if (isGetterCall(path.node, ctx.scope))
3780
4013
  reactive = true;
3781
- } else if (path.isSpreadElement()) {} else if (!(path.isIdentifier() || path.isLiteral() || path.isTemplateLiteral() || path.isTemplateElement() || path.isBinaryExpression() || path.isLogicalExpression() || path.isConditionalExpression() || path.isObjectExpression() || path.isObjectProperty() || path.isArrayExpression() || path.isParenthesizedExpression() || path.node.type === "ChainExpression" || path.isUnaryExpression() && path.node.operator !== "delete" || path.isTSAsExpression() || path.isTSSatisfiesExpression() || path.isTSNonNullExpression() || path.isTSTypeAssertion())) {
4014
+ } else if ((path.isArrowFunctionExpression() || path.isFunctionExpression()) && ctx.callbacks.has(path.node)) {} else if (path.isSpreadElement()) {} else if (!(path.isIdentifier() || path.isLiteral() || path.isTemplateLiteral() || path.isTemplateElement() || path.isBinaryExpression() || path.isLogicalExpression() || path.isConditionalExpression() || path.isObjectExpression() || path.isObjectProperty() || path.isArrayExpression() || path.isParenthesizedExpression() || path.node.type === "ChainExpression" || path.isUnaryExpression() && path.node.operator !== "delete" || path.isTSAsExpression() || path.isTSSatisfiesExpression() || path.isTSNonNullExpression() || path.isTSTypeAssertion())) {
3782
4015
  safe = false;
3783
4016
  }
3784
4017
  };
3785
- inspect(init);
4018
+ inspect(root);
3786
4019
  if (safe)
3787
- init.traverse({ enter: inspect });
4020
+ root.traverse({ enter: inspect });
4021
+ return { safe, reactive };
4022
+ }
4023
+ function isDerivedInitializer(declaration, scope, names, derived, pureImports) {
4024
+ const init = singlePath(declaration.get("init"));
4025
+ init.assertExpression();
4026
+ const { safe, reactive } = inspectDerivedExpression(init, declaration.node.start ?? Infinity, {
4027
+ scope,
4028
+ names,
4029
+ derived,
4030
+ pureImports,
4031
+ callbacks: new Set,
4032
+ visiting: new Set
4033
+ });
3788
4034
  return safe && reactive;
3789
4035
  }
3790
4036
  function promoteReactiveConstInitializers(ast, options) {
@@ -3873,8 +4119,196 @@ function promoteReactiveConstInitializers(ast, options) {
3873
4119
  }
3874
4120
  });
3875
4121
  }
4122
+ var readerParamCache = new Map;
4123
+ function unwrapTypeNode(node) {
4124
+ if (!node)
4125
+ return null;
4126
+ if (node.type === "TSTypeAnnotation" || node.type === "TSParenthesizedType") {
4127
+ return unwrapTypeNode(node.typeAnnotation ?? null);
4128
+ }
4129
+ return node;
4130
+ }
4131
+ function typeReferencesLiveRead(node) {
4132
+ const typeNode = unwrapTypeNode(node);
4133
+ if (!typeNode)
4134
+ return false;
4135
+ if (typeNode.type === "TSTypeReference") {
4136
+ const name = typeNode.typeName;
4137
+ return isIdentifier(name) && name.name === "LiveRead";
4138
+ }
4139
+ if (typeNode.type === "TSUnionType" || typeNode.type === "TSIntersectionType") {
4140
+ const types = typeNode.types ?? [];
4141
+ return types.some((part) => typeReferencesLiveRead(part));
4142
+ }
4143
+ return false;
4144
+ }
4145
+ function readerParamIndexes(params) {
4146
+ const indexes = [];
4147
+ for (const [index, param] of (params ?? []).entries()) {
4148
+ if (!param || param.type === "RestElement")
4149
+ continue;
4150
+ const annotated = param.type === "AssignmentPattern" ? param.left : param;
4151
+ const annotation = param.typeAnnotation ?? annotated?.typeAnnotation;
4152
+ if (typeReferencesLiveRead(annotation))
4153
+ indexes.push(index);
4154
+ }
4155
+ return indexes;
4156
+ }
4157
+ function functionParamsOf(node) {
4158
+ if (!node || !isFunctionDeclaration(node) && !isFunctionExpression(node) && !isArrowFunctionExpression(node)) {
4159
+ return null;
4160
+ }
4161
+ return node.params;
4162
+ }
4163
+ function scanReaderExports(filePath, resolveImport, depth = 0) {
4164
+ const cached = freshScan(readerParamCache, filePath);
4165
+ if (cached)
4166
+ return cached;
4167
+ const found = new Map;
4168
+ const scan = { value: found, stamps: new Map([[filePath, fileStamp(filePath)]]) };
4169
+ readerParamCache.set(filePath, scan);
4170
+ if (depth > 10)
4171
+ return scan;
4172
+ let code;
4173
+ try {
4174
+ code = fs2.readFileSync(filePath, "utf-8");
4175
+ } catch {
4176
+ return scan;
4177
+ }
4178
+ let ast;
4179
+ try {
4180
+ ast = parse(code, {
4181
+ sourceType: "module",
4182
+ plugins: ["jsx", "typescript"],
4183
+ sourceFilename: filePath
4184
+ });
4185
+ } catch {
4186
+ return scan;
4187
+ }
4188
+ const record = (name, params) => {
4189
+ if (!params)
4190
+ return;
4191
+ const indexes = readerParamIndexes(params);
4192
+ if (indexes.length > 0)
4193
+ found.set(name, indexes);
4194
+ };
4195
+ traverse(ast, {
4196
+ ExportNamedDeclaration(p) {
4197
+ const decl = p.node.declaration;
4198
+ if (isFunctionDeclaration(decl) && decl.id && isIdentifier(decl.id)) {
4199
+ record(decl.id.name, decl.params);
4200
+ } else if (decl && isVariableDeclaration(decl)) {
4201
+ for (const declarator of decl.declarations) {
4202
+ if (!isIdentifier(declarator.id))
4203
+ continue;
4204
+ record(declarator.id.name, functionParamsOf(declarator.init));
4205
+ }
4206
+ }
4207
+ const source = moduleSourceValue(p.node.source);
4208
+ if (!source)
4209
+ return;
4210
+ const resolved = resolveAnyImport(source, filePath, resolveImport);
4211
+ if (!resolved)
4212
+ return;
4213
+ const nested = scanReaderExports(resolved, resolveImport, depth + 1);
4214
+ mergeStamps(scan.stamps, nested.stamps);
4215
+ for (const spec of p.node.specifiers ?? []) {
4216
+ if (!isExportSpecifier(spec))
4217
+ continue;
4218
+ const exported = importedBindingName(spec.exported);
4219
+ const local = importedBindingName(spec.local);
4220
+ if (!exported || !local)
4221
+ continue;
4222
+ const indexes = nested.value.get(local);
4223
+ if (indexes)
4224
+ found.set(exported, indexes);
4225
+ }
4226
+ }
4227
+ });
4228
+ return scan;
4229
+ }
4230
+ function readerIndexesForCallee(path, imported) {
4231
+ const callee = unwrapParens(path.node.callee);
4232
+ if (!isIdentifier(callee))
4233
+ return [];
4234
+ const binding = path.scope.getBinding(callee.name);
4235
+ if (!binding)
4236
+ return [];
4237
+ if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier()) {
4238
+ return imported.get(callee.name) ?? [];
4239
+ }
4240
+ if (binding.path.isFunctionDeclaration()) {
4241
+ return readerParamIndexes(binding.path.node.params);
4242
+ }
4243
+ if (binding.path.isVariableDeclarator()) {
4244
+ return readerParamIndexes(functionParamsOf(binding.path.node.init) ?? []);
4245
+ }
4246
+ return [];
4247
+ }
4248
+ function wrapLiveReadArguments(ast, options) {
4249
+ const names = trackReactiveImports2(ast);
4250
+ const imported = new Map;
4251
+ const filename = options.filename ? path2.resolve(options.filename) : "";
4252
+ traverse(ast, {
4253
+ ImportDeclaration(p) {
4254
+ const source = moduleSourceValue(p.node.source);
4255
+ if (!source || !filename)
4256
+ return;
4257
+ const resolved = resolveAnyImport(source, filename, options.resolveImport);
4258
+ if (!resolved)
4259
+ return;
4260
+ const exports = scanReaderExports(resolved, options.resolveImport).value;
4261
+ for (const spec of p.node.specifiers ?? []) {
4262
+ if (isImportSpecifier(spec)) {
4263
+ const exported = importedBindingName(spec.imported);
4264
+ const indexes = exported ? exports.get(exported) : undefined;
4265
+ if (indexes)
4266
+ imported.set(spec.local.name, indexes);
4267
+ } else if (isImportDefaultSpecifier(spec)) {
4268
+ const indexes = exports.get("default");
4269
+ if (indexes)
4270
+ imported.set(spec.local.name, indexes);
4271
+ }
4272
+ }
4273
+ }
4274
+ });
4275
+ const { functions } = collectComponentFunctions2(ast, names.cc);
4276
+ const scopes = new Map;
4277
+ traverse(ast, {
4278
+ CallExpression(callPath) {
4279
+ const indexes = readerIndexesForCallee(callPath, imported);
4280
+ if (indexes.length === 0)
4281
+ return;
4282
+ const fnPath = callPath.getFunctionParent();
4283
+ const fn = fnPath?.node;
4284
+ if (!fnPath || !fn || !isFunction(fn) || !functions.has(fn))
4285
+ return;
4286
+ let scope = scopes.get(fn);
4287
+ if (!scope) {
4288
+ scope = { bindings: new Map };
4289
+ trackPropBindings(scope, fn.params[0], getAllPropNames2(fn.params[0]));
4290
+ trackLocalScopeBindings(fnPath, names, scope, true);
4291
+ scopes.set(fn, scope);
4292
+ }
4293
+ const argPaths = callPath.get("arguments");
4294
+ const args = Array.isArray(argPaths) ? argPaths : [argPaths];
4295
+ for (const index of indexes) {
4296
+ const argPath = args[index];
4297
+ if (!argPath || argPath.node.type === "SpreadElement")
4298
+ continue;
4299
+ const expr = unwrapParens(argPath.node);
4300
+ if (!isExpression(expr) || isFunctionLike(expr))
4301
+ continue;
4302
+ if (!containsReactiveRead(expr, scope))
4303
+ continue;
4304
+ argPath.replaceWith(arrowFunctionExpression([], argPath.node));
4305
+ }
4306
+ }
4307
+ });
4308
+ }
3876
4309
  function wrapReactiveExpressions2(ast, options = {}) {
3877
4310
  promoteReactiveConstInitializers(ast, options);
4311
+ wrapLiveReadArguments(ast, options);
3878
4312
  let metadata = null;
3879
4313
  if (options.analyzeMetadata) {
3880
4314
  metadata = options.analyzeMetadata;
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  runAnalyzeCli2,
5
5
  runCliIfMain2
6
- } from "./chunk-rshqq0t3.js";
6
+ } from "./chunk-52vtv5h0.js";
7
7
  export {
8
8
  runAnalyzeCli2 as runAnalyzeCli,
9
9
  runCliIfMain2 as runCliIfMain
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ import {
64
64
  propagateReactiveProps2,
65
65
  wrapReactiveExpressions2,
66
66
  runAnalyzeCli2
67
- } from "./chunk-rshqq0t3.js";
67
+ } from "./chunk-52vtv5h0.js";
68
68
 
69
69
  // src/live-cc-destructure.ts
70
70
  var CC_SOURCE_MODULES = new Set(["sinwan", "sinwan/component"]);
@@ -216,7 +216,7 @@ function hasLocalSpecifier(stmt, local) {
216
216
  function ensureComponentImport(ast, imported, local) {
217
217
  let target = null;
218
218
  for (const stmt of ast.program.body) {
219
- if (!isImportDeclaration(stmt))
219
+ if (!isImportDeclaration(stmt) || stmt.importKind === "type")
220
220
  continue;
221
221
  if (hasLocalSpecifier(stmt, local))
222
222
  return;
package/docs/api.md CHANGED
@@ -29,7 +29,7 @@ See [Transform](transform.md) for wrapping rules and options.
29
29
 
30
30
  ### `autoWrapComponents(ast, filename?)`
31
31
 
32
- Mutates an ESTree AST (oxc-parser): wrap exported component-like functions with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
32
+ Mutates an ESTree AST (oxc-parser): wrap exported component-like functions (including `function X` / `const X` exported through `export { X }`) with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
33
33
 
34
34
  ### `rewriteLiveCcDestructure(ast)`
35
35
 
package/docs/transform.md CHANGED
@@ -32,7 +32,7 @@ Exported functions that look like components are wrapped for you. You can write
32
32
 
33
33
  A candidate must be:
34
34
 
35
- - **Exported** (named or default)
35
+ - **Exported** (named or default): `export function`, `export const`, `export default`, or a top-level `function` / `const` exported through `export { X }` or `export { X as Name }` (the exported name must be uppercase)
36
36
  - **Uppercase** (or an anonymous `export default function ()`)
37
37
  - **0 or 1 parameter**
38
38
  - **Returning JSX** in that function (not only in a nested helper)
@@ -50,7 +50,9 @@ export const App = cc(function App() {
50
50
  });
51
51
  ```
52
52
 
53
- Not wrapped: lowercase helpers, generators, multi-argument functions, or functions that never return JSX. If `cc` is not imported yet, the compiler adds `import { cc } from "sinwan/component"`.
53
+ Not wrapped: lowercase helpers, generators, multi-argument functions, functions that never return JSX, type-only exports, re-exports (`export { X } from "./x"`), components that are not exported, and a `function X` exported through `export { X }` that module code reads before its declaration (for example `const Group = Object.assign(X, { Item })` above it), because turning it into `const X = cc(...)` would make that read throw. Reads inside functions, export specifiers and types do not count. A `cc` parent passes reactive props to child components as getters, so a non-exported component that receives props from one must be wrapped with `cc(...)` explicitly. If `cc` is not imported yet, the compiler adds `import { cc } from "sinwan/component"`.
54
+
55
+ Type parameters and return types are kept on the wrapped function. A circular import that reads `X` at module load before this file finishes evaluating is not detected; `export function X` has the same limitation.
54
56
 
55
57
  `cc` may be imported from `sinwan` or `sinwan/component`.
56
58
 
@@ -138,7 +140,10 @@ return (
138
140
 
139
141
  - Only referenced, identifier-named `const` declarations directly in a `cc` setup body (including auto-`cc` components) are candidates. Dependencies are processed in declaration order, including multiple declarators in one statement.
140
142
  - Pure read expressions are supported: member/index/optional access, arithmetic, comparisons, boolean/conditional expressions, template strings, object/array literals, and TypeScript expression annotations. Direct imported `useState` getter calls and unshadowed single-argument `Boolean`, `Number`, and `String` coercions are also supported. Property reads and coercions must be free of user-defined side effects.
141
- - Calls to arbitrary helpers, hooks, factories or methods, assignments, updates, allocations, destructuring, `let`/`var`, mutable captures, and forward/TDZ references remain eager — **unless** the callee is an imported function annotated with `/** @sinwan-pure */` (see [`@sinwan-pure` JSDoc contract](#sinwan-pure-jsdoc-contract) below). Explicit getter and `computed` initializers are unchanged. Module, event-handler, render-callback, loop and nested-block declarations are not promoted.
143
+ - Calls to arbitrary helpers, hooks, factories or methods, assignments, updates, allocations, destructuring, `let`/`var`, mutable captures, and forward/TDZ references remain eager — **unless** the callee is a function annotated with `/** @sinwan-pure */` (imported, or declared in the same file), a local zero-arity arrow getter whose expression body is itself safe (`const yearNow = () => …`, then `props.fromYear ?? yearNow() - 10`), or a built-in read on a known array or `Date` (below). See [`@sinwan-pure` JSDoc contract](#sinwan-pure-jsdoc-contract) below. A getter with a block body, side effects (`() => count++`) or a call back into itself stays eager. Explicit getter and `computed` initializers are unchanged.
144
+ - Built-in reads: `Array.from(...)`, non-mutating array reads (`map`, `filter`, `flatMap`, `flat`, `slice`, `concat`, `join`, `at`, `find`, `findIndex`, `some`, `every`, `reduce`, `includes`, `indexOf`, `entries`, `keys`, `values`) and `Date` getters (`getFullYear`, `getMonth`, `getDate`, `getDay`, `getTime`, `getHours`, `getMinutes`, `getSeconds`, `getMilliseconds`, `valueOf`) promote only on a receiver the compiler knows is an array or `Date`: an array literal, `Array.from(...)`, an array read that returns a new array, a `const` annotated `T[]` / `Array<T>` / `ReadonlyArray<T>` / `Date` or initialized from one of these, or a call to a same-file function with that return type annotation. A callback must be inline (its body is inspected) or a `@sinwan-pure` function; `items.map(track)` with an unannotated `track` stays eager. Reads on other values (`props.items.map(...)`, `props.query.filter(...)`) stay eager because the method may not be the built-in one; annotate a `const` (`const items: Item[] = props.items`) to opt in.
145
+ - A pure method read on a promoted local such as `monthList.map(...)` or `today.getFullYear()` is rewritten to `monthList().map(...)` / `today().getFullYear()`. Mutating reads such as `list.push` and `date.setFullYear` stay snapshots. A promoted local that holds a function is still called: `const close = props.onClose; close()` becomes `close()()`. Module, event-handler, render-callback, loop and nested-block declarations are not promoted.
146
+ - A reactive value passed to a parameter typed `LiveRead<T>` (`T | (() => T)`) is wrapped as `() => value`. An argument that is already a function is left alone. `createLiveState(..., props.selected)` therefore re-reads `props.selected` on each `state.value` access.
142
147
  - Bare signal/store containers still pass through unchanged. Bare component prop-bag aliases retain their ordinary JavaScript value semantics. References are resolved by lexical binding, so shadowed locals and object keys are not rewritten.
143
148
  - Every ordinary value use is called, including chained initializers, closures, event bodies, shorthand object values and setup `if` tests. A boolean does **not** become an accidentally truthy function. Setup control flow still runs once; use `Show`/`Switch` for reactive branches.
144
149
  - Variables used as JSX tag names, in type queries, or as mutation/method-call receivers are left eager. Components containing direct `eval` are excluded.
@@ -170,12 +175,12 @@ export function cn(...inputs: ClassValue[]): string {
170
175
  }
171
176
  ```
172
177
 
173
- Both `export function` and `export const` declarations are recognized. The annotation must be a block comment (`/** */`) immediately before the `export` keyword.
178
+ Both `export function` and `export const` declarations are recognized. The annotation must be a block comment (`/** */`) immediately before the `export` keyword. A non-exported function in the same file is also recognized when the comment sits directly above `function` or `const`.
174
179
 
175
180
  **What the compiler guarantees:**
176
181
 
177
182
  - When `derivedLocals: true` is enabled, calls to `@sinwan-pure`-annotated imported functions are treated as safe expressions. The containing `const` initializer is promoted to a zero-argument getter, and references inside the function arguments are rewritten (e.g. `seamInactive` → `seamInactive()`).
178
- - The compiler resolves the import path, reads the source file, parses it, and checks for `/** @sinwan-pure */` on exported declarations. Results are cached per resolved file path, so each file is read at most once per compilation process.
183
+ - The compiler resolves the import path, reads the source file, parses it, and checks for `/** @sinwan-pure */` on exported declarations. Results are cached per resolved file path and reused while that file, and any file it re-exports from, keeps its modification time, so a dev server picks up a new or removed annotation without a restart. The same applies to the `LiveRead<T>` parameter scan.
179
184
  - All import types are scanned: relative imports (`./`, `../`), bare specifiers (`clsx`, `sinwan/reactivity`), scoped packages (`@scope/utils`), and subpath imports (`@scope/utils/merge`). The compiler walks up the directory tree to find `node_modules/<pkg>`, reads `package.json`, and resolves the entry point via `exports`, `module`, `main`, `source`, or `index` files.
180
185
  - If the `resolveImport` callback is provided by the plugin, it is used for path resolution (handles tsconfig paths, bunfig aliases, workspace packages, and Bun/Vite module resolution). Otherwise, the compiler falls back to direct filesystem resolution with common extensions (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts`) and `index` files.
181
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinwan-compiler",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shared compiler core for Sinwan plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",