@rainfw/core 0.1.2 → 0.2.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/dist/client/dom.d.ts +4 -0
- package/dist/client/dom.d.ts.map +1 -0
- package/dist/client/dom.js +130 -0
- package/dist/client/dom.js.map +1 -0
- package/dist/client/hooks.d.ts +58 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +173 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/hydrate.d.ts +14 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/hydrate.js +167 -0
- package/dist/client/hydrate.js.map +1 -0
- package/dist/client/jsx-runtime.d.ts +6 -0
- package/dist/client/jsx-runtime.d.ts.map +1 -0
- package/dist/client/jsx-runtime.js +20 -0
- package/dist/client/jsx-runtime.js.map +1 -0
- package/dist/client/reconciler.d.ts +4 -0
- package/dist/client/reconciler.d.ts.map +1 -0
- package/dist/client/reconciler.js +238 -0
- package/dist/client/reconciler.js.map +1 -0
- package/dist/client/runtime.d.ts +6 -0
- package/dist/client/runtime.d.ts.map +1 -0
- package/dist/client/runtime.js +17 -0
- package/dist/client/runtime.js.map +1 -0
- package/dist/client/scheduler.d.ts +4 -0
- package/dist/client/scheduler.d.ts.map +1 -0
- package/dist/client/scheduler.js +44 -0
- package/dist/client/scheduler.js.map +1 -0
- package/dist/compiler/inject.d.ts +6 -0
- package/dist/compiler/inject.d.ts.map +1 -0
- package/dist/compiler/inject.js +19 -0
- package/dist/compiler/inject.js.map +1 -0
- package/dist/compiler/server-action.d.ts +9 -0
- package/dist/compiler/server-action.d.ts.map +1 -0
- package/dist/compiler/server-action.js +98 -0
- package/dist/compiler/server-action.js.map +1 -0
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/jsx/index.d.ts +3 -2
- package/dist/jsx/index.d.ts.map +1 -1
- package/dist/jsx/index.js +2 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/render.d.ts +7 -1
- package/dist/jsx/render.d.ts.map +1 -1
- package/dist/jsx/render.js +96 -14
- package/dist/jsx/render.js.map +1 -1
- package/dist/jsx/types.d.ts +4 -0
- package/dist/jsx/types.d.ts.map +1 -1
- package/dist/jsx/types.js +10 -0
- package/dist/jsx/types.js.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +84 -3
- package/dist/router.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
- package/scripts/dev.js +28 -1
- package/scripts/generate.js +228 -10
package/scripts/generate.js
CHANGED
|
@@ -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) {
|
|
@@ -166,6 +173,111 @@ function middlewarePathToDir(filePath) {
|
|
|
166
173
|
.replace(/\/$/, "");
|
|
167
174
|
}
|
|
168
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
|
+
|
|
274
|
+
function stripRouteGroupSegments(filePath) {
|
|
275
|
+
return filePath
|
|
276
|
+
.split("/")
|
|
277
|
+
.filter((segment) => !/^\(.+\)$/.test(segment))
|
|
278
|
+
.join("/");
|
|
279
|
+
}
|
|
280
|
+
|
|
169
281
|
function routePathToDir(filePath) {
|
|
170
282
|
return filePath
|
|
171
283
|
.replace(/\\/g, "/")
|
|
@@ -180,6 +292,7 @@ function middlewareImportName(filePath) {
|
|
|
180
292
|
.replace(/\//g, "_")
|
|
181
293
|
.replace(/\[/g, "$")
|
|
182
294
|
.replace(/\]/g, "")
|
|
295
|
+
.replace(/[()]/g, "")
|
|
183
296
|
.replace(/-/g, "_")
|
|
184
297
|
.replace(/_+$/, "");
|
|
185
298
|
return `mw_${base || "root"}`;
|
|
@@ -202,6 +315,7 @@ function layoutPathToDir(filePath) {
|
|
|
202
315
|
function pageFilePathToUrlPath(filePath) {
|
|
203
316
|
let urlPath = filePath.replace(/\.tsx?$/, "");
|
|
204
317
|
urlPath = urlPath.replace(/\\/g, "/");
|
|
318
|
+
urlPath = stripRouteGroupSegments(urlPath);
|
|
205
319
|
urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
206
320
|
urlPath = urlPath.replace(/\/page$/, "");
|
|
207
321
|
if (urlPath === "page") urlPath = "";
|
|
@@ -217,6 +331,7 @@ function pageFilePathToImportName(filePath) {
|
|
|
217
331
|
.replace(/\//g, "_")
|
|
218
332
|
.replace(/\[/g, "$")
|
|
219
333
|
.replace(/\]/g, "")
|
|
334
|
+
.replace(/[()]/g, "")
|
|
220
335
|
.replace(/-/g, "_")
|
|
221
336
|
);
|
|
222
337
|
}
|
|
@@ -228,6 +343,7 @@ function layoutImportName(filePath) {
|
|
|
228
343
|
.replace(/\//g, "_")
|
|
229
344
|
.replace(/\[/g, "$")
|
|
230
345
|
.replace(/\]/g, "")
|
|
346
|
+
.replace(/[()]/g, "")
|
|
231
347
|
.replace(/-/g, "_")
|
|
232
348
|
.replace(/_+$/, "");
|
|
233
349
|
return `layout_${base || "root"}`;
|
|
@@ -297,6 +413,63 @@ function validateNoPageRouteColocation(routeFiles, pageFiles) {
|
|
|
297
413
|
}
|
|
298
414
|
}
|
|
299
415
|
|
|
416
|
+
const routeUrlMap = new Map();
|
|
417
|
+
for (const f of routeFiles) {
|
|
418
|
+
routeUrlMap.set(filePathToUrlPath(f), f);
|
|
419
|
+
}
|
|
420
|
+
for (const pageFile of pageFiles) {
|
|
421
|
+
const url = pageFilePathToUrlPath(pageFile);
|
|
422
|
+
const conflicting = routeUrlMap.get(url);
|
|
423
|
+
if (conflicting) {
|
|
424
|
+
const pageDir = pageFilePathToDir(pageFile);
|
|
425
|
+
if (!routeDirs.has(pageDir)) {
|
|
426
|
+
errors.push(
|
|
427
|
+
`[Rain] Error: page "${pageFile}" and route "${conflicting}" resolve to the same URL path "${url}":\n` +
|
|
428
|
+
" → This conflict occurs because route group folders are stripped from URLs.\n" +
|
|
429
|
+
" → Move one of them to a different URL path.",
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return errors;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function validateNoDuplicateUrls(routeFiles, pageFiles) {
|
|
439
|
+
const errors = [];
|
|
440
|
+
|
|
441
|
+
const routeUrlMap = new Map();
|
|
442
|
+
for (const f of routeFiles) {
|
|
443
|
+
const url = filePathToUrlPath(f);
|
|
444
|
+
if (routeUrlMap.has(url)) {
|
|
445
|
+
errors.push(
|
|
446
|
+
`[Rain] Error: multiple route files resolve to the same URL path "${url}":\n` +
|
|
447
|
+
` → ${routeUrlMap.get(url)}\n` +
|
|
448
|
+
` → ${f}\n` +
|
|
449
|
+
" → Route group folders are stripped from URLs.\n" +
|
|
450
|
+
" → Rename one of the routes to avoid the conflict.",
|
|
451
|
+
);
|
|
452
|
+
} else {
|
|
453
|
+
routeUrlMap.set(url, f);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const pageUrlMap = new Map();
|
|
458
|
+
for (const f of pageFiles) {
|
|
459
|
+
const url = pageFilePathToUrlPath(f);
|
|
460
|
+
if (pageUrlMap.has(url)) {
|
|
461
|
+
errors.push(
|
|
462
|
+
`[Rain] Error: multiple page files resolve to the same URL path "${url}":\n` +
|
|
463
|
+
` → ${pageUrlMap.get(url)}\n` +
|
|
464
|
+
` → ${f}\n` +
|
|
465
|
+
" → Route group folders are stripped from URLs.\n" +
|
|
466
|
+
" → Rename one of the pages to avoid the conflict.",
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
pageUrlMap.set(url, f);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
300
473
|
return errors;
|
|
301
474
|
}
|
|
302
475
|
|
|
@@ -324,6 +497,7 @@ function getMiddlewaresForRoute(routeFile, middlewareFiles) {
|
|
|
324
497
|
function filePathToUrlPath(filePath) {
|
|
325
498
|
let urlPath = filePath.replace(/\.tsx?$/, "");
|
|
326
499
|
urlPath = urlPath.replace(/\\/g, "/");
|
|
500
|
+
urlPath = stripRouteGroupSegments(urlPath);
|
|
327
501
|
urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
328
502
|
urlPath = urlPath.replace(/\/route$/, "");
|
|
329
503
|
if (urlPath === "route") urlPath = "";
|
|
@@ -339,6 +513,7 @@ function filePathToImportName(filePath) {
|
|
|
339
513
|
.replace(/\//g, "_")
|
|
340
514
|
.replace(/\[/g, "$")
|
|
341
515
|
.replace(/\]/g, "")
|
|
516
|
+
.replace(/[()]/g, "")
|
|
342
517
|
.replace(/-/g, "_")
|
|
343
518
|
);
|
|
344
519
|
}
|
|
@@ -546,13 +721,9 @@ function validateCompatibilityFlags() {
|
|
|
546
721
|
if (!fs.existsSync(wranglerPath)) return;
|
|
547
722
|
|
|
548
723
|
const content = fs.readFileSync(wranglerPath, "utf-8");
|
|
549
|
-
const flagsMatch = content.match(
|
|
550
|
-
/compatibility_flags\s*=\s*\[([^\]]*)\]/,
|
|
551
|
-
);
|
|
724
|
+
const flagsMatch = content.match(/compatibility_flags\s*=\s*\[([^\]]*)\]/);
|
|
552
725
|
const flags = flagsMatch
|
|
553
|
-
? (flagsMatch[1].match(/"([^"]+)"/g) || []).map((s) =>
|
|
554
|
-
s.replace(/"/g, ""),
|
|
555
|
-
)
|
|
726
|
+
? (flagsMatch[1].match(/"([^"]+)"/g) || []).map((s) => s.replace(/"/g, ""))
|
|
556
727
|
: [];
|
|
557
728
|
|
|
558
729
|
if (!(flags.includes("nodejs_compat") || flags.includes("nodejs_als"))) {
|
|
@@ -571,6 +742,37 @@ function validateCompatibilityFlags() {
|
|
|
571
742
|
}
|
|
572
743
|
}
|
|
573
744
|
|
|
745
|
+
function buildAppInitLine(clientScripts, hasConfig) {
|
|
746
|
+
if (hasConfig) {
|
|
747
|
+
return clientScripts.length > 0
|
|
748
|
+
? `const app = new Rain({ ...config, clientScripts: ${JSON.stringify(clientScripts)} });`
|
|
749
|
+
: "const app = new Rain(config);";
|
|
750
|
+
}
|
|
751
|
+
return clientScripts.length > 0
|
|
752
|
+
? `const app = new Rain({ clientScripts: ${JSON.stringify(clientScripts)} });`
|
|
753
|
+
: "const app = new Rain();";
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function regenerateClient() {
|
|
757
|
+
const srcDir = path.join(PROJECT_ROOT, "src");
|
|
758
|
+
const clientFiles = getClientFiles(srcDir);
|
|
759
|
+
const clientScripts = bundleClientFilesSync(clientFiles, srcDir);
|
|
760
|
+
|
|
761
|
+
if (!fs.existsSync(ENTRY_FILE)) return;
|
|
762
|
+
|
|
763
|
+
const content = fs.readFileSync(ENTRY_FILE, "utf-8");
|
|
764
|
+
const hasConfig = fs.existsSync(CONFIG_FILE);
|
|
765
|
+
const appInit = buildAppInitLine(clientScripts, hasConfig);
|
|
766
|
+
const updated = content.replace(/^const app = new Rain\(.*\);$/m, appInit);
|
|
767
|
+
if (updated !== content) {
|
|
768
|
+
fs.writeFileSync(ENTRY_FILE, updated);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const clientMsg =
|
|
772
|
+
clientFiles.length > 0 ? `${clientFiles.length} client` : "0 client";
|
|
773
|
+
console.log(`[gen:client] ${clientMsg} -> .rainjs/entry.ts`);
|
|
774
|
+
}
|
|
775
|
+
|
|
574
776
|
function generate() {
|
|
575
777
|
if (!fs.existsSync(ROUTES_DIR)) {
|
|
576
778
|
console.error(
|
|
@@ -618,6 +820,12 @@ function generate() {
|
|
|
618
820
|
process.exit(1);
|
|
619
821
|
}
|
|
620
822
|
|
|
823
|
+
const duplicateErrors = validateNoDuplicateUrls(files, pageFiles);
|
|
824
|
+
for (const err of duplicateErrors) {
|
|
825
|
+
console.error(err);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
|
|
621
829
|
const hasRootLayout = layoutFiles.some((f) => layoutPathToDir(f) === "");
|
|
622
830
|
|
|
623
831
|
processMiddlewares(middlewareFiles, imports);
|
|
@@ -632,6 +840,10 @@ function generate() {
|
|
|
632
840
|
registrations,
|
|
633
841
|
);
|
|
634
842
|
|
|
843
|
+
const srcDir = path.join(PROJECT_ROOT, "src");
|
|
844
|
+
const clientFiles = getClientFiles(srcDir);
|
|
845
|
+
const clientScripts = bundleClientFilesSync(clientFiles, srcDir);
|
|
846
|
+
|
|
635
847
|
const hasConfig = fs.existsSync(CONFIG_FILE);
|
|
636
848
|
const fwPkg = BUILD_CONFIG.frameworkPackage;
|
|
637
849
|
const frameworkImport =
|
|
@@ -647,9 +859,7 @@ function generate() {
|
|
|
647
859
|
headerImports.push(`import config from "${configPath}";`);
|
|
648
860
|
}
|
|
649
861
|
|
|
650
|
-
const appInit = hasConfig
|
|
651
|
-
? "const app = new Rain(config);"
|
|
652
|
-
: "const app = new Rain();";
|
|
862
|
+
const appInit = buildAppInitLine(clientScripts, hasConfig);
|
|
653
863
|
|
|
654
864
|
const content = [
|
|
655
865
|
...headerImports,
|
|
@@ -665,18 +875,22 @@ function generate() {
|
|
|
665
875
|
|
|
666
876
|
fs.writeFileSync(ENTRY_FILE, content);
|
|
667
877
|
const total = files.length + pageFiles.length;
|
|
878
|
+
const clientMsg =
|
|
879
|
+
clientFiles.length > 0 ? `, ${clientFiles.length} client` : "";
|
|
668
880
|
console.log(
|
|
669
|
-
`[gen] ${total} route(s) (${files.length} api, ${pageFiles.length} page, ${layoutFiles.length} layout) -> .rainjs/entry.ts`,
|
|
881
|
+
`[gen] ${total} route(s) (${files.length} api, ${pageFiles.length} page, ${layoutFiles.length} layout${clientMsg}) -> .rainjs/entry.ts`,
|
|
670
882
|
);
|
|
671
883
|
}
|
|
672
884
|
|
|
673
885
|
module.exports = {
|
|
674
886
|
generate,
|
|
887
|
+
regenerateClient,
|
|
675
888
|
loadBuildConfig,
|
|
676
889
|
getRouteFiles,
|
|
677
890
|
getMiddlewareFiles,
|
|
678
891
|
getPageFiles,
|
|
679
892
|
getLayoutFiles,
|
|
893
|
+
getClientFiles,
|
|
680
894
|
getMiddlewaresForRoute,
|
|
681
895
|
getLayoutsForPage,
|
|
682
896
|
filePathToUrlPath,
|
|
@@ -691,7 +905,11 @@ module.exports = {
|
|
|
691
905
|
detectMiddlewareExportFromContent,
|
|
692
906
|
detectDefaultExport,
|
|
693
907
|
detectDefaultExportFromContent,
|
|
908
|
+
detectUseClientDirective,
|
|
909
|
+
bundleClientFilesSync,
|
|
694
910
|
validateNoPageRouteColocation,
|
|
911
|
+
validateNoDuplicateUrls,
|
|
912
|
+
stripRouteGroupSegments,
|
|
695
913
|
ROUTES_DIR,
|
|
696
914
|
ENTRY_FILE,
|
|
697
915
|
HTTP_METHODS,
|