waku 0.12.0 → 0.13.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 (63) hide show
  1. package/README.md +5 -2
  2. package/dist/cjs/cli.js +5 -5
  3. package/dist/cjs/client.js +1 -1
  4. package/dist/cjs/config.js +25 -3
  5. package/dist/cjs/lib/builder.js +25 -34
  6. package/dist/cjs/lib/config.js +16 -9
  7. package/dist/cjs/lib/middleware/devServer.js +22 -20
  8. package/dist/cjs/lib/middleware/rsc/utils.js +11 -0
  9. package/dist/cjs/lib/middleware/rsc/worker-api.js +51 -26
  10. package/dist/cjs/lib/middleware/rsc/worker-impl.js +54 -55
  11. package/dist/cjs/lib/middleware/rsc.js +29 -16
  12. package/dist/cjs/lib/middleware/ssr/utils.js +23 -11
  13. package/dist/cjs/lib/middleware/ssr.js +3 -3
  14. package/dist/cjs/lib/vite-plugin/rsc-index-plugin.js +1 -1
  15. package/dist/cjs/main.js +4 -0
  16. package/dist/cjs/router/client.js +2 -2
  17. package/dist/cjs/router/server.js +2 -2
  18. package/dist/cjs/server.js +22 -0
  19. package/dist/cli.js +5 -5
  20. package/dist/client.js +1 -1
  21. package/dist/config.d.ts +52 -2
  22. package/dist/config.js +13 -0
  23. package/dist/lib/builder.js +25 -34
  24. package/dist/lib/config.d.ts +4 -2
  25. package/dist/lib/config.js +16 -9
  26. package/dist/lib/middleware/devServer.js +23 -21
  27. package/dist/lib/middleware/rsc/utils.d.ts +1 -0
  28. package/dist/lib/middleware/rsc/utils.js +8 -0
  29. package/dist/lib/middleware/rsc/worker-api.d.ts +9 -2
  30. package/dist/lib/middleware/rsc/worker-api.js +51 -26
  31. package/dist/lib/middleware/rsc/worker-impl.js +55 -56
  32. package/dist/lib/middleware/rsc.d.ts +4 -2
  33. package/dist/lib/middleware/rsc.js +29 -16
  34. package/dist/lib/middleware/ssr/utils.d.ts +1 -1
  35. package/dist/lib/middleware/ssr/utils.js +24 -12
  36. package/dist/lib/middleware/ssr.d.ts +1 -1
  37. package/dist/lib/middleware/ssr.js +3 -3
  38. package/dist/lib/vite-plugin/rsc-index-plugin.js +1 -1
  39. package/dist/main.d.ts +1 -0
  40. package/dist/main.js +1 -0
  41. package/dist/router/client.js +1 -1
  42. package/dist/router/server.d.ts +1 -1
  43. package/dist/router/server.js +2 -2
  44. package/dist/server.d.ts +7 -2
  45. package/dist/server.js +17 -0
  46. package/package.json +25 -21
  47. package/src/cli.ts +13 -5
  48. package/src/client.ts +1 -1
  49. package/src/config.ts +68 -5
  50. package/src/lib/builder.ts +39 -44
  51. package/src/lib/config.ts +18 -7
  52. package/src/lib/middleware/devServer.ts +20 -16
  53. package/src/lib/middleware/rsc/utils.ts +9 -0
  54. package/src/lib/middleware/rsc/worker-api.ts +63 -31
  55. package/src/lib/middleware/rsc/worker-impl.ts +92 -68
  56. package/src/lib/middleware/rsc.ts +30 -13
  57. package/src/lib/middleware/ssr/utils.ts +30 -13
  58. package/src/lib/middleware/ssr.ts +11 -7
  59. package/src/lib/vite-plugin/rsc-index-plugin.ts +1 -1
  60. package/src/main.ts +1 -0
  61. package/src/router/client.ts +1 -1
  62. package/src/router/server.ts +4 -4
  63. package/src/server.ts +30 -3
package/README.md CHANGED
@@ -10,6 +10,8 @@ Minimalistic React Framework
10
10
 
11
11
  ## How to start a new project
12
12
 
13
+ Minimum requirement: Node.js 18
14
+
13
15
  ```bash
14
16
  npm create waku@latest
15
17
  ```
@@ -29,9 +31,8 @@ pnpm create waku # pnpm not working for now
29
31
  - [x] `waku start` (production server)
30
32
  - [x] Exportable build
31
33
  - [x] Static site generation
32
- - [ ] Client rendering fallback
33
34
  - [x] Opt-in router (reference implementation)
34
- - [ ] Opt-in SSR (HTML generation & hydration)
35
+ - [x] Opt-in SSR (HTML generation only)
35
36
 
36
37
  ## Tweets
37
38
 
@@ -72,6 +73,8 @@ pnpm create waku # pnpm not working for now
72
73
  - https://twitter.com/dai_shi/status/1664286329763684353
73
74
  - https://twitter.com/dai_shi/status/1664989534889861123
74
75
  - https://twitter.com/dai_shi/status/1667545252654366721
76
+ - https://twitter.com/dai_shi/status/1670650381762961408
77
+ - https://twitter.com/dai_shi/status/1671161795061628930
75
78
 
76
79
  </details>
77
80
 
package/dist/cjs/cli.js CHANGED
@@ -114,12 +114,12 @@ async function runDev(options) {
114
114
  const { devServer } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/devServer.js")));
115
115
  const app = express();
116
116
  app.use(rsc({
117
- mode: "development"
117
+ command: "dev"
118
118
  }));
119
119
  if (options?.ssr) {
120
120
  const { ssr } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/ssr.js")));
121
121
  app.use(ssr({
122
- mode: "development"
122
+ command: "dev"
123
123
  }));
124
124
  }
125
125
  app.use(devServer());
@@ -139,15 +139,15 @@ async function runStart(options) {
139
139
  const { rsc } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/rsc.js")));
140
140
  const app = express();
141
141
  app.use(rsc({
142
- mode: "production"
142
+ command: "start"
143
143
  }));
144
144
  if (options?.ssr) {
145
145
  const { ssr } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/ssr.js")));
146
146
  app.use(ssr({
147
- mode: "production"
147
+ command: "start"
148
148
  }));
149
149
  }
150
- app.use(express.static(_nodepath.default.join(config.root, config.framework.outPublic)));
150
+ app.use(express.static(_nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir)));
151
151
  express.static.mime.default_type = "";
152
152
  const port = process.env.PORT || 8080;
153
153
  app.listen(port, ()=>{
@@ -1,4 +1,4 @@
1
- /// <reference types="react/next" />
1
+ /// <reference types="react/canary" />
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
@@ -2,12 +2,34 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "defineConfig", {
6
- enumerable: true,
7
- get: function() {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ defineConfig: function() {
8
13
  return defineConfig;
14
+ },
15
+ unstable_setRootDir: function() {
16
+ return unstable_setRootDir;
17
+ },
18
+ unstable_rootDir: function() {
19
+ return unstable_rootDir;
9
20
  }
10
21
  });
11
22
  function defineConfig(config) {
12
23
  return config;
13
24
  }
25
+ function unstable_setRootDir(root) {
26
+ // FIXME it would be better to use a module variable or async local storage
27
+ globalThis.WAKU_CONFIG_ROOT_DIR = root;
28
+ }
29
+ function unstable_rootDir() {
30
+ const resolvedRootDir = globalThis.WAKU_CONFIG_ROOT_DIR;
31
+ if (typeof resolvedRootDir !== "string") {
32
+ throw new Error("rootDir() is called before resolved");
33
+ }
34
+ return resolvedRootDir;
35
+ }
@@ -10,7 +10,6 @@ Object.defineProperty(exports, "build", {
10
10
  });
11
11
  const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
12
12
  const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
13
- const _nodemodule = require("node:module");
14
13
  const _nodecrypto = require("node:crypto");
15
14
  const _vite = require("vite");
16
15
  const _pluginreact = /*#__PURE__*/ _interop_require_default(require("@vitejs/plugin-react"));
@@ -101,8 +100,10 @@ const buildServerBundle = async (config, entriesFile, clientEntryFiles, serverEn
101
100
  "react-server"
102
101
  ]
103
102
  },
103
+ publicDir: false,
104
104
  build: {
105
105
  ssr: true,
106
+ outDir: config.framework.distDir,
106
107
  rollupOptions: {
107
108
  onwarn,
108
109
  input: {
@@ -138,7 +139,7 @@ const buildServerBundle = async (config, entriesFile, clientEntryFiles, serverEn
138
139
  return serverBuildOutput;
139
140
  };
140
141
  const buildClientBundle = async (config, clientEntryFiles)=>{
141
- const indexHtmlFile = _nodepath.default.join(config.root, config.framework.indexHtml);
142
+ const indexHtmlFile = _nodepath.default.join(config.root, config.framework.srcDir, config.framework.indexHtml);
142
143
  const clientBuildOutput = await (0, _vite.build)({
143
144
  ..._config.configFileConfig,
144
145
  plugins: [
@@ -146,8 +147,9 @@ const buildClientBundle = async (config, clientEntryFiles)=>{
146
147
  (0, _pluginreact.default)(),
147
148
  (0, _rscindexplugin.rscIndexPlugin)()
148
149
  ],
150
+ root: _nodepath.default.join(config.root, config.framework.srcDir),
149
151
  build: {
150
- outDir: _nodepath.default.join(config.build.outDir, config.framework.outPublic),
152
+ outDir: _nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir),
151
153
  rollupOptions: {
152
154
  onwarn,
153
155
  input: {
@@ -189,22 +191,24 @@ const emitRscFiles = async (config)=>{
189
191
  return Array.from(idSet || []);
190
192
  };
191
193
  const rscFileSet = new Set(); // XXX could be implemented better
192
- await Promise.all(Object.entries(buildConfig).map(async ([, { elements }])=>{
194
+ await Promise.all(Object.entries(buildConfig).map(async ([, { elements, ctx }])=>{
193
195
  for (const [rscId, props] of elements || []){
194
196
  // FIXME we blindly expect JSON.stringify usage is deterministic
195
197
  const serializedProps = JSON.stringify(props);
196
198
  const searchParams = new URLSearchParams();
197
199
  searchParams.set("props", serializedProps);
198
- const destFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, config.framework.rscPrefix + decodeURIComponent(rscId), decodeURIComponent(`${searchParams}`));
200
+ const destFile = _nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir, config.framework.rscPrefix + decodeURIComponent(rscId), decodeURIComponent(`${searchParams}`));
199
201
  if (!rscFileSet.has(destFile)) {
200
202
  rscFileSet.add(destFile);
201
203
  _nodefs.default.mkdirSync(_nodepath.default.dirname(destFile), {
202
204
  recursive: true
203
205
  });
204
- const pipeable = (0, _workerapi.renderRSC)({
206
+ const [pipeable] = await (0, _workerapi.renderRSC)({
205
207
  rscId,
206
208
  props
207
209
  }, {
210
+ command: "build",
211
+ ctx,
208
212
  moduleIdCallback: (id)=>addClientModule(rscId, serializedProps, id)
209
213
  });
210
214
  await new Promise((resolve, reject)=>{
@@ -222,27 +226,30 @@ const emitRscFiles = async (config)=>{
222
226
  rscFiles: Array.from(rscFileSet)
223
227
  };
224
228
  };
225
- const renderHtml = async (config, pathStr, htmlStr)=>{
226
- const ssrConfig = await (0, _workerapi.getSsrConfigRSC)(pathStr);
229
+ const renderHtml = async (config, pathStr, htmlStr, ctx)=>{
230
+ const ssrConfig = await (0, _workerapi.getSsrConfigRSC)(pathStr, "build");
227
231
  if (!ssrConfig) {
228
232
  return null;
229
233
  }
230
234
  const { splitHTML, getFallback } = config.framework.ssr;
231
235
  const [rscId, props] = ssrConfig.element;
232
- const pipeable = (0, _workerapi.renderRSC)({
236
+ const [pipeable] = await (0, _workerapi.renderRSC)({
233
237
  rscId,
234
238
  props
239
+ }, {
240
+ command: "build",
241
+ ctx
235
242
  });
236
243
  return (0, _utils1.renderHtmlToReadable)(htmlStr, pipeable, splitHTML, getFallback);
237
244
  };
238
245
  const emitHtmlFiles = async (config, buildConfig, getClientModules)=>{
239
246
  const basePrefix = config.base + config.framework.rscPrefix;
240
- const publicIndexHtmlFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, config.framework.indexHtml);
247
+ const publicIndexHtmlFile = _nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir, config.framework.indexHtml);
241
248
  const publicIndexHtml = _nodefs.default.readFileSync(publicIndexHtmlFile, {
242
249
  encoding: "utf8"
243
250
  });
244
- const htmlFiles = await Promise.all(Object.entries(buildConfig).map(async ([pathStr, { elements, customCode, skipSsr }])=>{
245
- const destFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, pathStr, pathStr.endsWith("/") ? "index.html" : "");
251
+ const htmlFiles = await Promise.all(Object.entries(buildConfig).map(async ([pathStr, { elements, customCode, ctx, skipSsr }])=>{
252
+ const destFile = _nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir, pathStr, pathStr.endsWith("/") ? "index.html" : "");
246
253
  let data = "";
247
254
  if (_nodefs.default.existsSync(destFile)) {
248
255
  data = _nodefs.default.readFileSync(destFile, {
@@ -272,9 +279,9 @@ const emitHtmlFiles = async (config, buildConfig, getClientModules)=>{
272
279
  const code = (0, _utils.generatePrefetchCode)(basePrefix, elementsForPrefetch, moduleIdsForPrefetch) + (customCode || "");
273
280
  if (code) {
274
281
  // HACK is this too naive to inject script code?
275
- data = data.replace(/<\/body>/, `<script>${code}</script></body>`);
282
+ data = data.replace(/<\/head>/, `<script>${code}</script></head>`);
276
283
  }
277
- const htmlReadable = !skipSsr && await renderHtml(config, pathStr, data);
284
+ const htmlReadable = !skipSsr && await renderHtml(config, pathStr, data, ctx);
278
285
  if (htmlReadable) {
279
286
  await new Promise((resolve, reject)=>{
280
287
  const stream = _nodefs.default.createWriteStream(destFile);
@@ -293,25 +300,10 @@ const emitHtmlFiles = async (config, buildConfig, getClientModules)=>{
293
300
  htmlFiles
294
301
  };
295
302
  };
296
- const emitPackageJson = (config)=>{
297
- const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__filename).toString());
298
- const origPackageJson = require1(_nodepath.default.join(config.root, "package.json"));
299
- const packageJson = {
300
- name: origPackageJson.name,
301
- version: origPackageJson.version,
302
- private: true,
303
- type: "module",
304
- scripts: {
305
- start: "waku start"
306
- },
307
- dependencies: origPackageJson.dependencies
308
- };
309
- _nodefs.default.writeFileSync(_nodepath.default.join(config.root, config.build.outDir, "package.json"), JSON.stringify(packageJson, null, 2));
310
- };
311
303
  const emitVercelOutput = (config, clientBuildOutput, rscFiles, htmlFiles)=>{
312
- const clientFiles = clientBuildOutput.output.map(({ fileName })=>_nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, fileName));
313
- const srcDir = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic);
314
- const dstDir = _nodepath.default.join(config.root, config.build.outDir, ".vercel", "output");
304
+ const clientFiles = clientBuildOutput.output.map(({ fileName })=>_nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir, fileName));
305
+ const srcDir = _nodepath.default.join(config.root, config.framework.distDir, config.framework.publicDir);
306
+ const dstDir = _nodepath.default.join(config.root, config.framework.distDir, ".vercel", "output");
315
307
  for (const file of [
316
308
  ...clientFiles,
317
309
  ...rscFiles,
@@ -364,13 +356,12 @@ const resolveFileName = (fname)=>{
364
356
  };
365
357
  async function build() {
366
358
  const config = await (0, _config.resolveConfig)("build");
367
- const entriesFile = resolveFileName(_nodepath.default.join(config.root, config.framework.entriesJs));
359
+ const entriesFile = resolveFileName(_nodepath.default.join(config.root, config.framework.srcDir, config.framework.entriesJs));
368
360
  const { clientEntryFiles, serverEntryFiles } = await analyzeEntries(entriesFile);
369
361
  await buildServerBundle(config, entriesFile, clientEntryFiles, serverEntryFiles);
370
362
  const clientBuildOutput = await buildClientBundle(config, clientEntryFiles);
371
363
  const { buildConfig, getClientModules, rscFiles } = await emitRscFiles(config);
372
364
  const { htmlFiles } = await emitHtmlFiles(config, buildConfig, getClientModules);
373
- emitPackageJson(config);
374
365
  // https://vercel.com/docs/build-output-api/v3
375
366
  // So far, only static sites are supported.
376
367
  emitVercelOutput(config, clientBuildOutput, rscFiles, htmlFiles);
@@ -18,16 +18,21 @@ _export(exports, {
18
18
  });
19
19
  const _vite = require("vite");
20
20
  const splitHTML = (htmlStr)=>{
21
- const startStr = "<!--placeholder-->";
22
- const endStr = "<!--/placeholder-->";
23
- const splitted = htmlStr.split(new RegExp(startStr + "[\\s\\S]*" + endStr));
24
- if (splitted.length !== 2) {
21
+ const P1 = [
22
+ "<!--placeholder1-->\\s*<div[^>]*>",
23
+ "</div>\\s*<!--/placeholder1-->"
24
+ ];
25
+ const P2 = [
26
+ "<!--placeholder2-->",
27
+ "<!--/placeholder2-->"
28
+ ];
29
+ const anyRE = "[\\s\\S]*";
30
+ const match = htmlStr.match(new RegExp(// prettier-ignore
31
+ "^(" + anyRE + P1[0] + ")" + anyRE + "(" + P1[1] + anyRE + P2[0] + ")" + anyRE + "(" + P2[1] + anyRE + ")$"));
32
+ if (match?.length !== 1 + 3) {
25
33
  throw new Error("Failed to split HTML");
26
34
  }
27
- return [
28
- splitted[0] + startStr,
29
- endStr + splitted[1]
30
- ];
35
+ return match.slice(1);
31
36
  };
32
37
  const getFallback = (id)=>{
33
38
  if (id.endsWith("#Waku_SSR_Capable_Link")) {
@@ -42,9 +47,11 @@ async function resolveConfig(command) {
42
47
  const origConfig = await (0, _vite.resolveConfig)(configFileConfig, command);
43
48
  const origFramework = origConfig.framework;
44
49
  const framework = {
50
+ srcDir: "src",
51
+ distDir: "dist",
52
+ publicDir: "public",
45
53
  indexHtml: "index.html",
46
54
  entriesJs: "entries.js",
47
- outPublic: "public",
48
55
  rscPrefix: "RSC/",
49
56
  ...origFramework,
50
57
  ssr: {
@@ -20,27 +20,29 @@ function _interop_require_default(obj) {
20
20
  };
21
21
  }
22
22
  function devServer() {
23
- const vitePromise = (0, _vite.createServer)({
24
- ..._config.configFileConfig,
25
- optimizeDeps: {
26
- include: [
27
- "react-server-dom-webpack/client"
23
+ const configPromise = (0, _config.resolveConfig)("serve");
24
+ const vitePromise = configPromise.then((config)=>(0, _vite.createServer)({
25
+ ..._config.configFileConfig,
26
+ root: _nodepath.default.join(config.root, config.framework.srcDir),
27
+ optimizeDeps: {
28
+ include: [
29
+ "react-server-dom-webpack/client"
30
+ ],
31
+ // FIXME without this, waku router has dual module hazard,
32
+ // and "Uncaught Error: Missing Router" happens.
33
+ exclude: [
34
+ "waku"
35
+ ]
36
+ },
37
+ plugins: [
38
+ // @ts-expect-error This expression is not callable.
39
+ (0, _pluginreact.default)(),
40
+ (0, _rscindexplugin.rscIndexPlugin)()
28
41
  ],
29
- // FIXME without this, waku router has dual module hazard,
30
- // and "Uncaught Error: Missing Router" happens.
31
- exclude: [
32
- "waku"
33
- ]
34
- },
35
- plugins: [
36
- // @ts-expect-error This expression is not callable.
37
- (0, _pluginreact.default)(),
38
- (0, _rscindexplugin.rscIndexPlugin)()
39
- ],
40
- server: {
41
- middlewareMode: true
42
- }
43
- });
42
+ server: {
43
+ middlewareMode: true
44
+ }
45
+ }));
44
46
  vitePromise.then((vite)=>{
45
47
  (0, _workerapi.registerReloadCallback)((type)=>vite.ws.send({
46
48
  type
@@ -20,6 +20,9 @@ _export(exports, {
20
20
  },
21
21
  transformRsfId: function() {
22
22
  return transformRsfId;
23
+ },
24
+ deepFreeze: function() {
25
+ return deepFreeze;
23
26
  }
24
27
  });
25
28
  const _nodebuffer = require("node:buffer");
@@ -75,3 +78,11 @@ const transformRsfId = (prefixToRemove)=>new _nodestream.Transform({
75
78
  callback(null, changed ? _nodebuffer.Buffer.from(lines.join("\n")) : chunk);
76
79
  }
77
80
  });
81
+ const deepFreeze = (x)=>{
82
+ if (typeof x === "object" && x !== null) {
83
+ Object.freeze(x);
84
+ for (const value of Object.values(x)){
85
+ deepFreeze(value);
86
+ }
87
+ }
88
+ };
@@ -60,32 +60,56 @@ function shutdown() {
60
60
  let nextId = 1;
61
61
  function renderRSC(input, options) {
62
62
  const id = nextId++;
63
- const passthrough = new _nodestream.PassThrough();
64
- messageCallbacks.set(id, (mesg)=>{
65
- if (mesg.type === "buf") {
66
- passthrough.write(Buffer.from(mesg.buf, mesg.offset, mesg.len));
67
- } else if (mesg.type === "moduleId") {
68
- options?.moduleIdCallback?.(mesg.moduleId);
69
- } else if (mesg.type === "end") {
70
- passthrough.end();
71
- messageCallbacks.delete(id);
72
- } else if (mesg.type === "err") {
73
- const err = mesg.err instanceof Error ? mesg.err : new Error(String(mesg.err));
74
- if (mesg.statusCode) {
75
- err.statusCode = mesg.statusCode;
63
+ let started = false;
64
+ return new Promise((resolve, reject)=>{
65
+ const passthrough = new _nodestream.PassThrough();
66
+ messageCallbacks.set(id, (mesg)=>{
67
+ if (mesg.type === "start") {
68
+ if (!started) {
69
+ started = true;
70
+ resolve([
71
+ passthrough,
72
+ mesg.ctx
73
+ ]);
74
+ } else {
75
+ throw new Error("already started");
76
+ }
77
+ } else if (mesg.type === "buf") {
78
+ if (!started) {
79
+ throw new Error("not yet started");
80
+ }
81
+ passthrough.write(Buffer.from(mesg.buf, mesg.offset, mesg.len));
82
+ } else if (mesg.type === "moduleId") {
83
+ options.moduleIdCallback?.(mesg.moduleId);
84
+ } else if (mesg.type === "end") {
85
+ if (!started) {
86
+ throw new Error("not yet started");
87
+ }
88
+ passthrough.end();
89
+ messageCallbacks.delete(id);
90
+ } else if (mesg.type === "err") {
91
+ const err = mesg.err instanceof Error ? mesg.err : new Error(String(mesg.err));
92
+ if (mesg.statusCode) {
93
+ err.statusCode = mesg.statusCode;
94
+ }
95
+ if (!started) {
96
+ reject(err);
97
+ } else {
98
+ passthrough.destroy(err);
99
+ }
100
+ messageCallbacks.delete(id);
76
101
  }
77
- passthrough.destroy(err);
78
- messageCallbacks.delete(id);
79
- }
102
+ });
103
+ const mesg = {
104
+ id,
105
+ type: "render",
106
+ input,
107
+ command: options.command,
108
+ ctx: options.ctx ?? null,
109
+ moduleIdCallback: !!options.moduleIdCallback
110
+ };
111
+ worker.postMessage(mesg);
80
112
  });
81
- const mesg = {
82
- id,
83
- type: "render",
84
- input,
85
- moduleIdCallback: !!options?.moduleIdCallback
86
- };
87
- worker.postMessage(mesg);
88
- return passthrough;
89
113
  }
90
114
  function getBuildConfigRSC() {
91
115
  return new Promise((resolve, reject)=>{
@@ -106,7 +130,7 @@ function getBuildConfigRSC() {
106
130
  worker.postMessage(mesg);
107
131
  });
108
132
  }
109
- function getSsrConfigRSC(pathStr) {
133
+ function getSsrConfigRSC(pathStr, command) {
110
134
  return new Promise((resolve, reject)=>{
111
135
  const id = nextId++;
112
136
  messageCallbacks.set(id, (mesg)=>{
@@ -121,7 +145,8 @@ function getSsrConfigRSC(pathStr) {
121
145
  const mesg = {
122
146
  id,
123
147
  type: "getSsrConfig",
124
- pathStr
148
+ pathStr,
149
+ command
125
150
  };
126
151
  worker.postMessage(mesg);
127
152
  });
@@ -19,9 +19,11 @@ function _interop_require_default(obj) {
19
19
  }
20
20
  const { renderToPipeableStream } = _server.default;
21
21
  const handleRender = async (mesg)=>{
22
- const { id, input, moduleIdCallback } = mesg;
22
+ const { id, input, command, ctx, moduleIdCallback } = mesg;
23
23
  try {
24
- const options = {};
24
+ const options = {
25
+ command
26
+ };
25
27
  if (moduleIdCallback) {
26
28
  options.moduleIdCallback = (moduleId)=>{
27
29
  const mesg = {
@@ -32,7 +34,15 @@ const handleRender = async (mesg)=>{
32
34
  _nodeworker_threads.parentPort.postMessage(mesg);
33
35
  };
34
36
  }
35
- const pipeable = await renderRSC(input, options);
37
+ const { runWithContext } = await loadServerFile("waku/server");
38
+ const pipeable = await runWithContext(ctx, ()=>renderRSC(input, options));
39
+ const mesg = {
40
+ id,
41
+ type: "start",
42
+ ctx
43
+ };
44
+ _nodeworker_threads.parentPort.postMessage(mesg);
45
+ (0, _utils.deepFreeze)(ctx);
36
46
  const writable = new _nodestream.Writable({
37
47
  write (chunk, encoding, callback) {
38
48
  if (encoding !== "buffer") {
@@ -93,9 +103,9 @@ const handleGetBuildConfig = async (mesg)=>{
93
103
  }
94
104
  };
95
105
  const handleGetSsrConfig = async (mesg)=>{
96
- const { id, pathStr } = mesg;
106
+ const { id, pathStr, command } = mesg;
97
107
  try {
98
- const output = await getSsrConfigRSC(pathStr);
108
+ const output = await getSsrConfigRSC(pathStr, command);
99
109
  const mesg = {
100
110
  id,
101
111
  type: "ssrConfig",
@@ -158,55 +168,39 @@ _nodeworker_threads.parentPort.on("message", (mesg)=>{
158
168
  handleGetSsrConfig(mesg);
159
169
  }
160
170
  });
161
- // FIXME using mutable module variable doesn't seem nice. Let's revisit this.
162
- let resolvedConfig;
163
- const getEntriesFile = async ()=>{
164
- if (!resolvedConfig) {
165
- throw new Error("config is not ready");
166
- }
167
- const config = resolvedConfig;
168
- if (config.command === "build") {
169
- return _nodepath.default.join(config.root, config.build.outDir, config.framework.entriesJs);
170
- }
171
- return _nodepath.default.join(config.root, config.framework.entriesJs);
171
+ const getEntriesFile = async (config, command)=>{
172
+ return _nodepath.default.join(config.root, command === "dev" ? config.framework.srcDir : config.framework.distDir, config.framework.entriesJs);
172
173
  };
173
- const getFunctionComponent = async (rscId)=>{
174
- const entriesFile = await getEntriesFile();
175
- const { default: { getEntry } } = await loadServerFile(entriesFile);
176
- const mod = await getEntry(rscId);
177
- if (typeof mod === "function") {
178
- return mod;
179
- }
180
- if (typeof mod?.default === "function") {
181
- return mod?.default;
182
- }
183
- const err = new Error("No function component found");
184
- err.statusCode = 404; // HACK our convention for NotFound
185
- throw err;
186
- };
187
- const resolveClientEntry = (filePath)=>{
188
- if (!resolvedConfig) {
189
- throw new Error("config is not ready");
190
- }
191
- const config = resolvedConfig;
192
- if (config.command === "build") {
193
- return config.base + _nodepath.default.relative(_nodepath.default.join(config.root, config.build.outDir), filePath);
194
- }
195
- if (config.mode === "development" && !filePath.startsWith(config.root)) {
174
+ const resolveClientEntry = (filePath, config, command)=>{
175
+ const root = _nodepath.default.join(config.root, command === "dev" ? config.framework.srcDir : config.framework.distDir);
176
+ if (command === "dev" && !filePath.startsWith(root)) {
196
177
  // HACK this relies on Vite's internal implementation detail.
197
178
  return config.base + "@fs" + filePath;
198
179
  }
199
- return config.base + _nodepath.default.relative(config.root, filePath);
180
+ return config.base + _nodepath.default.relative(root, filePath);
200
181
  };
201
182
  async function renderRSC(input, options) {
202
- if (!resolvedConfig) {
203
- resolvedConfig = await (0, _config.resolveConfig)("serve");
204
- }
205
- const config = resolvedConfig;
183
+ const config = await (0, _config.resolveConfig)(options.command === "build" ? "build" : "serve");
184
+ const { unstable_setRootDir } = await loadServerFile("waku/config");
185
+ unstable_setRootDir(config.root);
186
+ const getFunctionComponent = async (rscId)=>{
187
+ const entriesFile = await getEntriesFile(config, options.command);
188
+ const { default: { getEntry } } = await loadServerFile(entriesFile);
189
+ const mod = await getEntry(rscId);
190
+ if (typeof mod === "function") {
191
+ return mod;
192
+ }
193
+ if (typeof mod?.default === "function") {
194
+ return mod?.default;
195
+ }
196
+ const err = new Error("No function component found");
197
+ err.statusCode = 404; // HACK our convention for NotFound
198
+ throw err;
199
+ };
206
200
  const bundlerConfig = new Proxy({}, {
207
201
  get (_target, encodedId) {
208
202
  const [filePath, name] = encodedId.split("#");
209
- const id = resolveClientEntry(filePath);
203
+ const id = resolveClientEntry(filePath, config, options.command);
210
204
  options?.moduleIdCallback?.(id);
211
205
  return {
212
206
  id,
@@ -235,22 +229,27 @@ async function renderRSC(input, options) {
235
229
  throw new Error("Unexpected input");
236
230
  }
237
231
  async function getBuildConfigRSC() {
238
- if (!resolvedConfig) {
239
- resolvedConfig = await (0, _config.resolveConfig)("build");
240
- }
241
- const config = resolvedConfig;
242
- const distEntriesFile = await getEntriesFile();
243
- const { default: { getBuildConfig } } = await loadServerFile(distEntriesFile);
232
+ const config = await (0, _config.resolveConfig)("build");
233
+ const { unstable_setRootDir } = await loadServerFile("waku/config");
234
+ unstable_setRootDir(config.root);
235
+ const entriesFile = await getEntriesFile(config, "build");
236
+ const { default: { getBuildConfig } } = await loadServerFile(entriesFile);
244
237
  if (!getBuildConfig) {
245
238
  console.warn("getBuildConfig is undefined. It's recommended for optimization and sometimes required.");
246
239
  return {};
247
240
  }
248
- const output = await getBuildConfig(config.root, renderRSC);
241
+ const output = await getBuildConfig((input, options)=>renderRSC(input, {
242
+ ...options,
243
+ command: "build"
244
+ }));
249
245
  return output;
250
246
  }
251
- async function getSsrConfigRSC(pathStr) {
252
- const distEntriesFile = await getEntriesFile();
253
- const { default: { getSsrConfig } } = await loadServerFile(distEntriesFile);
247
+ async function getSsrConfigRSC(pathStr, command) {
248
+ const config = await (0, _config.resolveConfig)(command === "build" ? "build" : "serve");
249
+ const { unstable_setRootDir } = await loadServerFile("waku/config");
250
+ unstable_setRootDir(config.root);
251
+ const entriesFile = await getEntriesFile(config, command);
252
+ const { default: { getSsrConfig } } = await loadServerFile(entriesFile);
254
253
  if (!getSsrConfig) {
255
254
  return null;
256
255
  }