@rainfw/core 0.1.1 → 0.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.
Files changed (64) hide show
  1. package/README.md +28 -28
  2. package/dist/client/dom.d.ts +4 -0
  3. package/dist/client/dom.d.ts.map +1 -0
  4. package/dist/client/dom.js +130 -0
  5. package/dist/client/dom.js.map +1 -0
  6. package/dist/client/hooks.d.ts +58 -0
  7. package/dist/client/hooks.d.ts.map +1 -0
  8. package/dist/client/hooks.js +173 -0
  9. package/dist/client/hooks.js.map +1 -0
  10. package/dist/client/hydrate.d.ts +14 -0
  11. package/dist/client/hydrate.d.ts.map +1 -0
  12. package/dist/client/hydrate.js +167 -0
  13. package/dist/client/hydrate.js.map +1 -0
  14. package/dist/client/jsx-runtime.d.ts +6 -0
  15. package/dist/client/jsx-runtime.d.ts.map +1 -0
  16. package/dist/client/jsx-runtime.js +20 -0
  17. package/dist/client/jsx-runtime.js.map +1 -0
  18. package/dist/client/reconciler.d.ts +4 -0
  19. package/dist/client/reconciler.d.ts.map +1 -0
  20. package/dist/client/reconciler.js +238 -0
  21. package/dist/client/reconciler.js.map +1 -0
  22. package/dist/client/runtime.d.ts +6 -0
  23. package/dist/client/runtime.d.ts.map +1 -0
  24. package/dist/client/runtime.js +17 -0
  25. package/dist/client/runtime.js.map +1 -0
  26. package/dist/client/scheduler.d.ts +4 -0
  27. package/dist/client/scheduler.d.ts.map +1 -0
  28. package/dist/client/scheduler.js +44 -0
  29. package/dist/client/scheduler.js.map +1 -0
  30. package/dist/compiler/inject.d.ts +6 -0
  31. package/dist/compiler/inject.d.ts.map +1 -0
  32. package/dist/compiler/inject.js +19 -0
  33. package/dist/compiler/inject.js.map +1 -0
  34. package/dist/compiler/server-action.d.ts +9 -0
  35. package/dist/compiler/server-action.d.ts.map +1 -0
  36. package/dist/compiler/server-action.js +98 -0
  37. package/dist/compiler/server-action.js.map +1 -0
  38. package/dist/context.js +1 -1
  39. package/dist/context.js.map +1 -1
  40. package/dist/index.d.ts +5 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/jsx/index.d.ts +3 -2
  45. package/dist/jsx/index.d.ts.map +1 -1
  46. package/dist/jsx/index.js +2 -2
  47. package/dist/jsx/index.js.map +1 -1
  48. package/dist/jsx/render.d.ts +7 -1
  49. package/dist/jsx/render.d.ts.map +1 -1
  50. package/dist/jsx/render.js +117 -10
  51. package/dist/jsx/render.js.map +1 -1
  52. package/dist/jsx/types.d.ts +4 -0
  53. package/dist/jsx/types.d.ts.map +1 -1
  54. package/dist/jsx/types.js +10 -0
  55. package/dist/jsx/types.js.map +1 -1
  56. package/dist/router.d.ts +7 -1
  57. package/dist/router.d.ts.map +1 -1
  58. package/dist/router.js +84 -3
  59. package/dist/router.js.map +1 -1
  60. package/dist/types.d.ts +3 -0
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +5 -1
  63. package/scripts/dev.js +28 -1
  64. package/scripts/generate.js +192 -10
@@ -3,6 +3,13 @@ const path = require("node:path");
3
3
  const { execSync } = require("node:child_process");
4
4
  const ts = require("typescript");
5
5
 
6
+ let esbuild;
7
+ try {
8
+ esbuild = require("esbuild");
9
+ } catch (_esbuildOptional) {
10
+ esbuild = null;
11
+ }
12
+
6
13
  const PROJECT_ROOT = process.cwd();
7
14
 
8
15
  function unwrapExpression(node) {
@@ -27,7 +34,11 @@ function extractStringProps(obj, sourceFile, keys) {
27
34
 
28
35
  function loadBuildConfig() {
29
36
  const configPath = path.join(PROJECT_ROOT, "rain.config.ts");
30
- const defaults = { routesDir: "src/routes", outDir: ".rainjs" };
37
+ const defaults = {
38
+ routesDir: "src/routes",
39
+ outDir: ".rainjs",
40
+ frameworkPackage: "@rainfw/core",
41
+ };
31
42
  if (!fs.existsSync(configPath)) return defaults;
32
43
 
33
44
  const content = fs.readFileSync(configPath, "utf-8");
@@ -46,7 +57,11 @@ function loadBuildConfig() {
46
57
  if (!ts.isObjectLiteralExpression(obj)) return;
47
58
  Object.assign(
48
59
  config,
49
- extractStringProps(obj, sourceFile, ["routesDir", "outDir"]),
60
+ extractStringProps(obj, sourceFile, [
61
+ "routesDir",
62
+ "outDir",
63
+ "frameworkPackage",
64
+ ]),
50
65
  );
51
66
  });
52
67
 
@@ -158,6 +173,104 @@ function middlewarePathToDir(filePath) {
158
173
  .replace(/\/$/, "");
159
174
  }
160
175
 
176
+ function detectUseClientDirective(content) {
177
+ const sourceFile = ts.createSourceFile(
178
+ "file.tsx",
179
+ content,
180
+ ts.ScriptTarget.Latest,
181
+ true,
182
+ );
183
+ const firstStatement = sourceFile.statements[0];
184
+ if (!firstStatement) return false;
185
+ return (
186
+ ts.isExpressionStatement(firstStatement) &&
187
+ ts.isStringLiteral(firstStatement.expression) &&
188
+ firstStatement.expression.text === "use client"
189
+ );
190
+ }
191
+
192
+ function getClientFiles(dir, base = "") {
193
+ if (!fs.existsSync(dir)) return [];
194
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
195
+ const files = [];
196
+
197
+ for (const entry of entries) {
198
+ const fullPath = path.join(dir, entry.name);
199
+ const relativePath = path.join(base, entry.name);
200
+
201
+ if (entry.isDirectory()) {
202
+ if (entry.name === "node_modules" || entry.name === ".rainjs") continue;
203
+ files.push(...getClientFiles(fullPath, relativePath));
204
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
205
+ const content = fs.readFileSync(fullPath, "utf-8");
206
+ if (detectUseClientDirective(content)) {
207
+ files.push(relativePath);
208
+ }
209
+ }
210
+ }
211
+
212
+ return files;
213
+ }
214
+
215
+ function bundleClientFilesSync(clientFiles, srcDir) {
216
+ if (clientFiles.length === 0) return [];
217
+ if (!esbuild) {
218
+ console.warn(
219
+ "[Rain] Warning: esbuild not found.\n" +
220
+ " → Install esbuild to enable client bundling: npm install -D esbuild\n" +
221
+ " → Client-side components will not be bundled.",
222
+ );
223
+ return [];
224
+ }
225
+
226
+ const outDir = path.join(PROJECT_ROOT, "public", "_rain");
227
+ if (!fs.existsSync(outDir)) {
228
+ fs.mkdirSync(outDir, { recursive: true });
229
+ }
230
+
231
+ for (const file of fs.readdirSync(outDir)) {
232
+ if (file.startsWith("island-")) {
233
+ fs.unlinkSync(path.join(outDir, file));
234
+ }
235
+ }
236
+
237
+ const entryPoints = clientFiles.map((f) => path.join(srcDir, f));
238
+
239
+ const result = esbuild.buildSync({
240
+ entryPoints,
241
+ outdir: outDir,
242
+ bundle: true,
243
+ minify: true,
244
+ format: "esm",
245
+ metafile: true,
246
+ entryNames: "island-[hash]",
247
+ write: true,
248
+ treeShaking: true,
249
+ platform: "browser",
250
+ target: ["es2022"],
251
+ jsx: "automatic",
252
+ jsxImportSource: "@rainfw/core",
253
+ alias: {
254
+ "@rainfw/core/jsx-runtime": path.resolve(
255
+ PROJECT_ROOT,
256
+ "src/framework/client/jsx-runtime.ts",
257
+ ),
258
+ },
259
+ loader: { ".ts": "ts", ".tsx": "tsx" },
260
+ });
261
+
262
+ const publicDir = path.join(PROJECT_ROOT, "public");
263
+ const scripts = [];
264
+ for (const [outPath, meta] of Object.entries(result.metafile.outputs)) {
265
+ if (meta.entryPoint) {
266
+ const relPath = path.relative(publicDir, outPath);
267
+ scripts.push(`/${relPath.replace(/\\/g, "/")}`);
268
+ }
269
+ }
270
+
271
+ return scripts;
272
+ }
273
+
161
274
  function routePathToDir(filePath) {
162
275
  return filePath
163
276
  .replace(/\\/g, "/")
@@ -533,6 +646,63 @@ function processPages(
533
646
  }
534
647
  }
535
648
 
649
+ function validateCompatibilityFlags() {
650
+ const wranglerPath = path.join(PROJECT_ROOT, "wrangler.toml");
651
+ if (!fs.existsSync(wranglerPath)) return;
652
+
653
+ const content = fs.readFileSync(wranglerPath, "utf-8");
654
+ const flagsMatch = content.match(/compatibility_flags\s*=\s*\[([^\]]*)\]/);
655
+ const flags = flagsMatch
656
+ ? (flagsMatch[1].match(/"([^"]+)"/g) || []).map((s) => s.replace(/"/g, ""))
657
+ : [];
658
+
659
+ if (!(flags.includes("nodejs_compat") || flags.includes("nodejs_als"))) {
660
+ console.error(
661
+ "[Rain] Error: Missing required compatibility flag.\n" +
662
+ ' → Rain.js requires "nodejs_compat" in your wrangler.toml.\n' +
663
+ " → Add the following to your wrangler.toml:\n\n" +
664
+ ' compatibility_flags = ["nodejs_compat"]\n\n' +
665
+ " → Without this flag, the Workers runtime will fail with:\n" +
666
+ ' "No such module node:async_hooks"\n' +
667
+ " → See: https://developers.cloudflare.com/workers/" +
668
+ "configuration/compatibility-flags/" +
669
+ "#nodejs-compatibility-flag",
670
+ );
671
+ process.exit(1);
672
+ }
673
+ }
674
+
675
+ function buildAppInitLine(clientScripts, hasConfig) {
676
+ if (hasConfig) {
677
+ return clientScripts.length > 0
678
+ ? `const app = new Rain({ ...config, clientScripts: ${JSON.stringify(clientScripts)} });`
679
+ : "const app = new Rain(config);";
680
+ }
681
+ return clientScripts.length > 0
682
+ ? `const app = new Rain({ clientScripts: ${JSON.stringify(clientScripts)} });`
683
+ : "const app = new Rain();";
684
+ }
685
+
686
+ function regenerateClient() {
687
+ const srcDir = path.join(PROJECT_ROOT, "src");
688
+ const clientFiles = getClientFiles(srcDir);
689
+ const clientScripts = bundleClientFilesSync(clientFiles, srcDir);
690
+
691
+ if (!fs.existsSync(ENTRY_FILE)) return;
692
+
693
+ const content = fs.readFileSync(ENTRY_FILE, "utf-8");
694
+ const hasConfig = fs.existsSync(CONFIG_FILE);
695
+ const appInit = buildAppInitLine(clientScripts, hasConfig);
696
+ const updated = content.replace(/^const app = new Rain\(.*\);$/m, appInit);
697
+ if (updated !== content) {
698
+ fs.writeFileSync(ENTRY_FILE, updated);
699
+ }
700
+
701
+ const clientMsg =
702
+ clientFiles.length > 0 ? `${clientFiles.length} client` : "0 client";
703
+ console.log(`[gen:client] ${clientMsg} -> .rainjs/entry.ts`);
704
+ }
705
+
536
706
  function generate() {
537
707
  if (!fs.existsSync(ROUTES_DIR)) {
538
708
  console.error(
@@ -544,6 +714,8 @@ function generate() {
544
714
  process.exit(1);
545
715
  }
546
716
 
717
+ validateCompatibilityFlags();
718
+
547
719
  try {
548
720
  execSync("npx wrangler types", {
549
721
  cwd: PROJECT_ROOT,
@@ -592,12 +764,18 @@ function generate() {
592
764
  registrations,
593
765
  );
594
766
 
767
+ const srcDir = path.join(PROJECT_ROOT, "src");
768
+ const clientFiles = getClientFiles(srcDir);
769
+ const clientScripts = bundleClientFilesSync(clientFiles, srcDir);
770
+
595
771
  const hasConfig = fs.existsSync(CONFIG_FILE);
596
- const frameworkPath = relativeImportPath(
597
- path.join(PROJECT_ROOT, "src", "framework"),
598
- );
772
+ const fwPkg = BUILD_CONFIG.frameworkPackage;
773
+ const frameworkImport =
774
+ fwPkg.startsWith(".") || fwPkg.startsWith("/")
775
+ ? relativeImportPath(path.join(PROJECT_ROOT, fwPkg))
776
+ : fwPkg;
599
777
 
600
- const headerImports = [`import { Rain } from "${frameworkPath}";`];
778
+ const headerImports = [`import { Rain } from "${frameworkImport}";`];
601
779
  if (hasConfig) {
602
780
  const configPath = relativeImportPath(
603
781
  path.join(PROJECT_ROOT, "rain.config"),
@@ -605,9 +783,7 @@ function generate() {
605
783
  headerImports.push(`import config from "${configPath}";`);
606
784
  }
607
785
 
608
- const appInit = hasConfig
609
- ? "const app = new Rain(config);"
610
- : "const app = new Rain();";
786
+ const appInit = buildAppInitLine(clientScripts, hasConfig);
611
787
 
612
788
  const content = [
613
789
  ...headerImports,
@@ -623,18 +799,22 @@ function generate() {
623
799
 
624
800
  fs.writeFileSync(ENTRY_FILE, content);
625
801
  const total = files.length + pageFiles.length;
802
+ const clientMsg =
803
+ clientFiles.length > 0 ? `, ${clientFiles.length} client` : "";
626
804
  console.log(
627
- `[gen] ${total} route(s) (${files.length} api, ${pageFiles.length} page, ${layoutFiles.length} layout) -> .rainjs/entry.ts`,
805
+ `[gen] ${total} route(s) (${files.length} api, ${pageFiles.length} page, ${layoutFiles.length} layout${clientMsg}) -> .rainjs/entry.ts`,
628
806
  );
629
807
  }
630
808
 
631
809
  module.exports = {
632
810
  generate,
811
+ regenerateClient,
633
812
  loadBuildConfig,
634
813
  getRouteFiles,
635
814
  getMiddlewareFiles,
636
815
  getPageFiles,
637
816
  getLayoutFiles,
817
+ getClientFiles,
638
818
  getMiddlewaresForRoute,
639
819
  getLayoutsForPage,
640
820
  filePathToUrlPath,
@@ -649,6 +829,8 @@ module.exports = {
649
829
  detectMiddlewareExportFromContent,
650
830
  detectDefaultExport,
651
831
  detectDefaultExportFromContent,
832
+ detectUseClientDirective,
833
+ bundleClientFilesSync,
652
834
  validateNoPageRouteColocation,
653
835
  ROUTES_DIR,
654
836
  ENTRY_FILE,