@rainfw/core 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/package.json +5 -1
  2. package/scripts/generate.js +217 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainfw/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A TypeScript web framework for Cloudflare Workers",
5
5
  "bin": {
6
6
  "rainjs": "./cli/index.js"
@@ -58,6 +58,10 @@
58
58
  "./db": {
59
59
  "types": "./dist/db.d.ts",
60
60
  "import": "./dist/db.js"
61
+ },
62
+ "./client": {
63
+ "types": "./dist/client/hooks.d.ts",
64
+ "import": "./dist/client/hooks.js"
61
65
  }
62
66
  },
63
67
  "devDependencies": {
@@ -587,6 +587,65 @@ function detectExportedMethods(filePath) {
587
587
  return detectExportedMethodsFromContent(content);
588
588
  }
589
589
 
590
+ function collectAllExportedNames(node) {
591
+ if (ts.isVariableStatement(node)) {
592
+ return node.declarationList.declarations
593
+ .filter((d) => ts.isIdentifier(d.name))
594
+ .map((d) => d.name.text);
595
+ }
596
+ if (
597
+ (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) &&
598
+ node.name
599
+ ) {
600
+ return [node.name.text];
601
+ }
602
+ return [];
603
+ }
604
+
605
+ function collectAllNamedExports(node) {
606
+ if (
607
+ !(
608
+ ts.isExportDeclaration(node) &&
609
+ node.exportClause &&
610
+ ts.isNamedExports(node.exportClause)
611
+ )
612
+ ) {
613
+ return [];
614
+ }
615
+ return node.exportClause.elements.map((el) => el.name.text);
616
+ }
617
+
618
+ function detectAllExportsFromContent(content) {
619
+ const sourceFile = ts.createSourceFile(
620
+ "file.tsx",
621
+ content,
622
+ ts.ScriptTarget.Latest,
623
+ true,
624
+ );
625
+ const named = [];
626
+ let hasDefault = false;
627
+
628
+ ts.forEachChild(sourceFile, (node) => {
629
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
630
+ hasDefault = true;
631
+ return;
632
+ }
633
+ if (hasExportKeyword(node)) {
634
+ const modifiers = ts.canHaveModifiers(node)
635
+ ? ts.getModifiers(node)
636
+ : undefined;
637
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
638
+ hasDefault = true;
639
+ } else {
640
+ named.push(...collectAllExportedNames(node));
641
+ }
642
+ }
643
+ named.push(...collectAllNamedExports(node));
644
+ });
645
+
646
+ return { named, hasDefault };
647
+ }
648
+
590
649
  function detectMiddlewareExportFromContent(content) {
591
650
  return detectExportedNamesFromContent(content, ["onRequest"]).length > 0;
592
651
  }
@@ -773,6 +832,156 @@ function regenerateClient() {
773
832
  console.log(`[gen:client] ${clientMsg} -> .rainjs/entry.ts`);
774
833
  }
775
834
 
835
+ function clientFileToIslandId(relPath) {
836
+ return relPath
837
+ .replace(/\\/g, "/")
838
+ .replace(/\.tsx?$/, "")
839
+ .replace(/[^a-zA-Z0-9_/]/g, "_");
840
+ }
841
+
842
+ function generateIslandProxy(clientRelPath, srcDir, fwImport) {
843
+ const islandDir = path.join(PROJECT_ROOT, BUILD_CONFIG.outDir, "islands");
844
+ if (!fs.existsSync(islandDir)) {
845
+ fs.mkdirSync(islandDir, { recursive: true });
846
+ }
847
+
848
+ const fullPath = path.join(srcDir, clientRelPath);
849
+ const content = fs.readFileSync(fullPath, "utf-8");
850
+ const { named, hasDefault } = detectAllExportsFromContent(content);
851
+ const islandId = clientFileToIslandId(clientRelPath);
852
+
853
+ const proxyFile = path.join(
854
+ islandDir,
855
+ clientRelPath.replace(/\\/g, "/").replace(/\.tsx?$/, ".ts"),
856
+ );
857
+ const proxyDir = path.dirname(proxyFile);
858
+ if (!fs.existsSync(proxyDir)) {
859
+ fs.mkdirSync(proxyDir, { recursive: true });
860
+ }
861
+
862
+ const proxyEntryDir = path.dirname(proxyFile);
863
+ let relOriginal = path
864
+ .relative(
865
+ proxyEntryDir,
866
+ path.join(srcDir, clientRelPath.replace(/\.tsx?$/, "")),
867
+ )
868
+ .replace(/\\/g, "/");
869
+ if (!relOriginal.startsWith(".")) relOriginal = `./${relOriginal}`;
870
+
871
+ const lines = [];
872
+ lines.push(`import { markAsIsland } from "${fwImport}";`);
873
+
874
+ const importSpecifiers = [];
875
+ if (hasDefault) {
876
+ importSpecifiers.push("default as _default");
877
+ }
878
+ for (const name of named) {
879
+ importSpecifiers.push(`${name} as _${name}`);
880
+ }
881
+ if (importSpecifiers.length > 0) {
882
+ lines.push(
883
+ `import { ${importSpecifiers.join(", ")} } from "${relOriginal}";`,
884
+ );
885
+ }
886
+ lines.push("");
887
+
888
+ if (hasDefault) {
889
+ lines.push(`export default markAsIsland("${islandId}:default", _default);`);
890
+ }
891
+ for (const name of named) {
892
+ lines.push(
893
+ `export const ${name} = markAsIsland("${islandId}:${name}", _${name});`,
894
+ );
895
+ }
896
+ lines.push("");
897
+
898
+ fs.writeFileSync(proxyFile, lines.join("\n"));
899
+ return { proxyFile, islandId, named, hasDefault };
900
+ }
901
+
902
+ function generateAllIslandProxies(clientFiles, srcDir, fwImport) {
903
+ const islandDir = path.join(PROJECT_ROOT, BUILD_CONFIG.outDir, "islands");
904
+ if (fs.existsSync(islandDir)) {
905
+ fs.rmSync(islandDir, { recursive: true, force: true });
906
+ }
907
+
908
+ const proxies = [];
909
+ for (const cf of clientFiles) {
910
+ proxies.push(generateIslandProxy(cf, srcDir, fwImport));
911
+ }
912
+ return proxies;
913
+ }
914
+
915
+ function updateWranglerAliases(clientFiles, srcDir) {
916
+ const wranglerPath = path.join(PROJECT_ROOT, "wrangler.toml");
917
+ if (!fs.existsSync(wranglerPath)) return;
918
+
919
+ let content = fs.readFileSync(wranglerPath, "utf-8");
920
+
921
+ const markerStart = "# [rain:alias:start]";
922
+ const markerEnd = "# [rain:alias:end]";
923
+
924
+ const startIdx = content.indexOf(markerStart);
925
+ const endIdx = content.indexOf(markerEnd);
926
+ if (startIdx !== -1 && endIdx !== -1) {
927
+ content =
928
+ content.slice(0, startIdx).trimEnd() +
929
+ "\n" +
930
+ content.slice(endIdx + markerEnd.length).trimStart();
931
+ }
932
+
933
+ if (clientFiles.length === 0) {
934
+ const cleaned = `${content.trimEnd()}
935
+ `;
936
+ if (cleaned !== fs.readFileSync(wranglerPath, "utf-8")) {
937
+ fs.writeFileSync(wranglerPath, cleaned);
938
+ }
939
+ return;
940
+ }
941
+
942
+ const islandDir = path.join(PROJECT_ROOT, BUILD_CONFIG.outDir, "islands");
943
+
944
+ const aliasLines = [markerStart];
945
+ const hasExistingAlias = /^\[alias\]/m.test(content);
946
+ if (!hasExistingAlias) {
947
+ aliasLines.push("[alias]");
948
+ }
949
+
950
+ for (const cf of clientFiles) {
951
+ const originalAbs = path.join(srcDir, cf.replace(/\.tsx?$/, ""));
952
+ const proxyAbs = path.join(
953
+ islandDir,
954
+ cf.replace(/\\/g, "/").replace(/\.tsx?$/, ".ts"),
955
+ );
956
+ const relProxy = path.relative(PROJECT_ROOT, proxyAbs).replace(/\\/g, "/");
957
+ const relOriginal = path
958
+ .relative(PROJECT_ROOT, originalAbs)
959
+ .replace(/\\/g, "/");
960
+ aliasLines.push(`"./${relOriginal}" = "./${relProxy}"`);
961
+ }
962
+ aliasLines.push(markerEnd);
963
+
964
+ const aliasBlock = aliasLines.join("\n");
965
+
966
+ if (hasExistingAlias) {
967
+ const aliasIdx = content.search(/^\[alias\]/m);
968
+ let insertAt = content.indexOf("\n", aliasIdx);
969
+ if (insertAt === -1) insertAt = content.length;
970
+ content =
971
+ content.slice(0, insertAt + 1) +
972
+ aliasLines.filter((l) => l !== "[alias]").join("\n") +
973
+ "\n" +
974
+ content.slice(insertAt + 1);
975
+ } else {
976
+ content = `${content.trimEnd()}
977
+
978
+ ${aliasBlock}
979
+ `;
980
+ }
981
+
982
+ fs.writeFileSync(wranglerPath, content);
983
+ }
984
+
776
985
  function generate() {
777
986
  if (!fs.existsSync(ROUTES_DIR)) {
778
987
  console.error(
@@ -851,6 +1060,9 @@ function generate() {
851
1060
  ? relativeImportPath(path.join(PROJECT_ROOT, fwPkg))
852
1061
  : fwPkg;
853
1062
 
1063
+ generateAllIslandProxies(clientFiles, srcDir, frameworkImport);
1064
+ updateWranglerAliases(clientFiles, srcDir);
1065
+
854
1066
  const headerImports = [`import { Rain } from "${frameworkImport}";`];
855
1067
  if (hasConfig) {
856
1068
  const configPath = relativeImportPath(
@@ -906,6 +1118,11 @@ module.exports = {
906
1118
  detectDefaultExport,
907
1119
  detectDefaultExportFromContent,
908
1120
  detectUseClientDirective,
1121
+ detectAllExportsFromContent,
1122
+ generateIslandProxy,
1123
+ generateAllIslandProxies,
1124
+ updateWranglerAliases,
1125
+ clientFileToIslandId,
909
1126
  bundleClientFilesSync,
910
1127
  validateNoPageRouteColocation,
911
1128
  validateNoDuplicateUrls,