waku 0.11.2 → 0.11.3

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 +1 -0
  2. package/dist/cjs/cli.js +66 -23
  3. package/dist/cjs/client.js +4 -0
  4. package/dist/cjs/lib/builder.js +234 -95
  5. package/dist/cjs/lib/middleware/devServer.js +65 -0
  6. package/dist/cjs/lib/{rsc-handler.js → middleware/rsc/worker-api.js} +11 -31
  7. package/dist/cjs/lib/middleware/rsc/worker-impl.js +221 -0
  8. package/dist/cjs/lib/{middleware.js → middleware/rsc.js} +13 -59
  9. package/dist/cjs/lib/vite-plugin/rsc-analyze-plugin.js +85 -0
  10. package/dist/cjs/lib/vite-plugin/rsc-index-plugin.js +25 -0
  11. package/dist/cjs/lib/vite-plugin/rsc-reload-plugin.js +97 -0
  12. package/dist/cjs/lib/{vite-plugin-rsc.js → vite-plugin/rsc-transform-plugin.js} +3 -103
  13. package/dist/cjs/main.js +4 -3
  14. package/dist/cjs/router/server.js +7 -5
  15. package/dist/cli.js +66 -23
  16. package/dist/client.d.ts +1 -1
  17. package/dist/client.js +4 -0
  18. package/dist/lib/builder.js +233 -94
  19. package/dist/lib/middleware/devServer.d.ts +5 -0
  20. package/dist/lib/middleware/devServer.js +50 -0
  21. package/dist/lib/{rsc-handler.d.ts → middleware/rsc/worker-api.d.ts} +11 -16
  22. package/dist/lib/{rsc-handler.js → middleware/rsc/worker-api.js} +9 -26
  23. package/dist/lib/middleware/rsc/worker-impl.js +212 -0
  24. package/dist/lib/{middleware.d.ts → middleware/rsc.d.ts} +0 -1
  25. package/dist/lib/{middleware.js → middleware/rsc.js} +10 -48
  26. package/dist/lib/vite-plugin/rsc-analyze-plugin.d.ts +2 -0
  27. package/dist/lib/vite-plugin/rsc-analyze-plugin.js +31 -0
  28. package/dist/lib/vite-plugin/rsc-index-plugin.d.ts +2 -0
  29. package/dist/lib/vite-plugin/rsc-index-plugin.js +16 -0
  30. package/dist/lib/vite-plugin/rsc-reload-plugin.d.ts +2 -0
  31. package/dist/lib/vite-plugin/rsc-reload-plugin.js +43 -0
  32. package/dist/lib/vite-plugin/rsc-transform-plugin.d.ts +2 -0
  33. package/dist/lib/vite-plugin/rsc-transform-plugin.js +61 -0
  34. package/dist/main.d.ts +2 -1
  35. package/dist/main.js +2 -1
  36. package/dist/router/server.js +7 -5
  37. package/dist/server.d.ts +13 -1
  38. package/package.json +27 -17
  39. package/src/cli.ts +70 -24
  40. package/src/client.ts +4 -0
  41. package/src/lib/builder.ts +300 -122
  42. package/src/lib/middleware/devServer.ts +51 -0
  43. package/src/lib/{rsc-handler.ts → middleware/rsc/worker-api.ts} +26 -39
  44. package/src/lib/middleware/rsc/worker-impl.ts +223 -0
  45. package/src/lib/{middleware.ts → middleware/rsc.ts} +9 -48
  46. package/src/lib/vite-plugin/rsc-analyze-plugin.ts +34 -0
  47. package/src/lib/vite-plugin/rsc-index-plugin.ts +19 -0
  48. package/src/lib/vite-plugin/rsc-reload-plugin.ts +44 -0
  49. package/src/lib/vite-plugin/rsc-transform-plugin.ts +55 -0
  50. package/src/main.ts +2 -1
  51. package/src/router/server.ts +5 -12
  52. package/src/server.ts +19 -3
  53. package/src/types.d.ts +2 -1
  54. package/dist/cjs/lib/rsc-handler-worker.js +0 -327
  55. package/dist/lib/rsc-handler-worker.js +0 -318
  56. package/dist/lib/vite-plugin-rsc.d.ts +0 -5
  57. package/dist/lib/vite-plugin-rsc.js +0 -147
  58. package/src/lib/rsc-handler-worker.ts +0 -358
  59. package/src/lib/vite-plugin-rsc.ts +0 -145
  60. /package/dist/cjs/lib/{rsc-utils.js → middleware/rsc/utils.js} +0 -0
  61. /package/dist/lib/{rsc-utils.d.ts → middleware/rsc/utils.d.ts} +0 -0
  62. /package/dist/lib/{rsc-utils.js → middleware/rsc/utils.js} +0 -0
  63. /package/dist/lib/{rsc-handler-worker.d.ts → middleware/rsc/worker-impl.d.ts} +0 -0
  64. /package/src/lib/{rsc-utils.ts → middleware/rsc/utils.ts} +0 -0
package/README.md CHANGED
@@ -70,6 +70,7 @@ pnpm create waku # pnpm not working for now
70
70
  - https://twitter.com/dai_shi/status/1660660331528728578
71
71
  - https://twitter.com/dai_shi/status/1661727138746339328
72
72
  - https://twitter.com/dai_shi/status/1664286329763684353
73
+ - https://twitter.com/dai_shi/status/1664989534889861123
73
74
 
74
75
  </details>
75
76
 
package/dist/cjs/cli.js CHANGED
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
7
7
  const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
8
+ const _nodeutil = require("node:util");
9
+ const _nodemodule = require("node:module");
8
10
  function _interop_require_default(obj) {
9
11
  return obj && obj.__esModule ? obj : {
10
12
  default: obj
@@ -49,34 +51,60 @@ function _interop_require_wildcard(obj, nodeInterop) {
49
51
  }
50
52
  return newObj;
51
53
  }
52
- const cmd = process.argv[2];
53
- for(let i = 3; i < process.argv.length; ++i){
54
- if (process.argv[i] === "--config") {
55
- const fname = process.argv[i + 1];
56
- if (fname && _nodefs.default.existsSync(fname)) {
57
- process.env.CONFIG_FILE = fname;
58
- } else {
59
- throw new Error("config file does not exist");
54
+ const require1 = (0, _nodemodule.createRequire)(new URL(".", require("url").pathToFileURL(__filename).toString()));
55
+ const { values , positionals } = (0, _nodeutil.parseArgs)({
56
+ args: process.argv.splice(2),
57
+ allowPositionals: true,
58
+ options: {
59
+ config: {
60
+ type: "string"
61
+ },
62
+ version: {
63
+ type: "boolean",
64
+ short: "v"
65
+ },
66
+ help: {
67
+ type: "boolean",
68
+ short: "h"
60
69
  }
61
- ++i;
70
+ }
71
+ });
72
+ const cmd = positionals[0];
73
+ if (values.config) {
74
+ if (!_nodefs.default.existsSync(values.config)) {
75
+ throw new Error("config file does not exist");
76
+ } else {
77
+ process.env.CONFIG_FILE = values.config;
62
78
  }
63
79
  }
64
- switch(cmd){
65
- case "dev":
66
- runDev();
67
- break;
68
- case "build":
69
- runBuild();
70
- break;
71
- case "start":
72
- runStart();
73
- break;
74
- default:
75
- throw Error("unknown cmd: " + cmd);
80
+ if (values.version) {
81
+ const { version } = require1("../package.json");
82
+ console.log(version);
83
+ } else if (values.help) {
84
+ displayUsage();
85
+ } else {
86
+ switch(cmd){
87
+ case "dev":
88
+ runDev();
89
+ break;
90
+ case "build":
91
+ runBuild();
92
+ break;
93
+ case "start":
94
+ runStart();
95
+ break;
96
+ default:
97
+ if (cmd) {
98
+ console.error("Unknown command:", cmd);
99
+ }
100
+ displayUsage();
101
+ break;
102
+ }
76
103
  }
77
104
  async function runDev() {
78
105
  const { default: express } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("express")));
79
- const { rsc , devServer } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware.js")));
106
+ const { rsc } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/rsc.js")));
107
+ const { devServer } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/devServer.js")));
80
108
  const app = express();
81
109
  app.use(rsc({
82
110
  mode: "development"
@@ -95,7 +123,7 @@ async function runStart() {
95
123
  const { default: express } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("express")));
96
124
  const { resolveConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/config.js")));
97
125
  const config = await resolveConfig("serve");
98
- const { rsc } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware.js")));
126
+ const { rsc } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("./lib/middleware/rsc.js")));
99
127
  const app = express();
100
128
  app.use(rsc({
101
129
  mode: "production"
@@ -107,3 +135,18 @@ async function runStart() {
107
135
  console.info("Listening on", port);
108
136
  });
109
137
  }
138
+ function displayUsage() {
139
+ console.log(`
140
+ Usage: waku [options] <command>
141
+
142
+ Commands:
143
+ dev Start the development server
144
+ build Build the application for production
145
+ start Start the production server
146
+
147
+ Options:
148
+ -c, --config <path> Path to the configuration file
149
+ -v, --version Display the version number
150
+ -h, --help Display this help message
151
+ `);
152
+ }
@@ -64,6 +64,10 @@ function serve(rscId, basePath = "/RSC/") {
64
64
  ];
65
65
  });
66
66
  const ServerComponent = (props)=>{
67
+ if (!props) {
68
+ console.warn("Something went wrong. Please refresh your browser.");
69
+ return;
70
+ }
67
71
  // FIXME we blindly expect JSON.stringify usage is deterministic
68
72
  const serializedProps = JSON.stringify(props);
69
73
  const [data, setRerender] = fetchRSC(serializedProps);
@@ -11,121 +11,99 @@ Object.defineProperty(exports, "build", {
11
11
  const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
12
12
  const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
13
13
  const _nodemodule = require("node:module");
14
+ const _nodecrypto = require("node:crypto");
14
15
  const _vite = require("vite");
15
16
  const _pluginreact = /*#__PURE__*/ _interop_require_default(require("@vitejs/plugin-react"));
16
17
  const _config = require("./config.js");
17
- const _rschandler = require("./rsc-handler.js");
18
- const _vitepluginrsc = require("./vite-plugin-rsc.js");
18
+ const _utils = require("./middleware/rsc/utils.js");
19
+ const _workerapi = require("./middleware/rsc/worker-api.js");
20
+ const _rscindexplugin = require("./vite-plugin/rsc-index-plugin.js");
21
+ const _rscanalyzeplugin = require("./vite-plugin/rsc-analyze-plugin.js");
19
22
  function _interop_require_default(obj) {
20
23
  return obj && obj.__esModule ? obj : {
21
24
  default: obj
22
25
  };
23
26
  }
24
- const createVercelOutput = (config, clientFiles, rscFiles, htmlFiles)=>{
25
- const srcDir = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic);
26
- const dstDir = _nodepath.default.join(config.root, config.build.outDir, ".vercel", "output");
27
- for (const file of [
28
- ...clientFiles,
29
- ...rscFiles,
30
- ...htmlFiles
31
- ]){
32
- const dstFile = _nodepath.default.join(dstDir, "static", _nodepath.default.relative(srcDir, file));
33
- if (!_nodefs.default.existsSync(dstFile)) {
34
- _nodefs.default.mkdirSync(_nodepath.default.dirname(dstFile), {
35
- recursive: true
36
- });
37
- _nodefs.default.symlinkSync(_nodepath.default.relative(_nodepath.default.dirname(dstFile), file), dstFile);
38
- }
39
- }
40
- const overrides = Object.fromEntries([
41
- ...rscFiles.filter((file)=>!_nodepath.default.extname(file)).map((file)=>[
42
- _nodepath.default.relative(srcDir, file),
43
- {
44
- contentType: "text/plain"
45
- }
46
- ]),
47
- ...htmlFiles.filter((file)=>!_nodepath.default.extname(file)).map((file)=>[
48
- _nodepath.default.relative(srcDir, file),
49
- {
50
- contentType: "text/html"
51
- }
52
- ])
53
- ]);
54
- const configJson = {
55
- version: 3,
56
- overrides
57
- };
58
- _nodefs.default.mkdirSync(dstDir, {
59
- recursive: true
60
- });
61
- _nodefs.default.writeFileSync(_nodepath.default.join(dstDir, "config.json"), JSON.stringify(configJson, null, 2));
62
- };
63
- const resolveFileName = (fname)=>{
64
- for (const ext of [
65
- ".js",
66
- ".ts",
67
- ".tsx",
68
- ".jsx"
69
- ]){
70
- const resolvedName = fname.slice(0, fname.length - _nodepath.default.extname(fname).length) + ext;
71
- if (_nodefs.default.existsSync(resolvedName)) {
72
- return resolvedName;
73
- }
27
+ // Upstream issue: https://github.com/rollup/rollup/issues/4699
28
+ const onwarn = (warning, warn)=>{
29
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && /"use (client|server)"/.test(warning.message)) {
30
+ return;
31
+ } else if (warning.code === "SOURCEMAP_ERROR" && warning.loc?.file?.endsWith(".tsx") && warning.loc?.column === 0 && warning.loc?.line === 1) {
32
+ return;
74
33
  }
75
- return "";
34
+ warn(warning);
76
35
  };
77
- async function build() {
78
- const config = await (0, _config.resolveConfig)("build");
79
- const indexHtmlFile = _nodepath.default.join(config.root, config.framework.indexHtml);
80
- const distEntriesFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.entriesJs);
81
- let entriesFile = _nodepath.default.join(config.root, config.framework.entriesJs);
82
- if (entriesFile.endsWith(".js")) {
83
- entriesFile = resolveFileName(entriesFile) || entriesFile;
84
- }
85
- const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__filename).toString());
36
+ const hash = (fname)=>new Promise((resolve)=>{
37
+ const sha256 = (0, _nodecrypto.createHash)("sha256");
38
+ sha256.on("readable", ()=>{
39
+ const data = sha256.read();
40
+ if (data) {
41
+ resolve(data.toString("hex").slice(0, 9));
42
+ }
43
+ });
44
+ _nodefs.default.createReadStream(fname).pipe(sha256);
45
+ });
46
+ const analyzeEntries = async (entriesFile)=>{
86
47
  const clientEntryFileSet = new Set();
87
48
  const serverEntryFileSet = new Set();
88
49
  await (0, _vite.build)({
89
50
  ..._config.configFileConfig,
90
51
  plugins: [
91
- (0, _vitepluginrsc.rscAnalyzePlugin)((id)=>clientEntryFileSet.add(id), (id)=>serverEntryFileSet.add(id))
52
+ (0, _rscanalyzeplugin.rscAnalyzePlugin)((id)=>clientEntryFileSet.add(id), (id)=>serverEntryFileSet.add(id))
92
53
  ],
93
54
  ssr: {
94
- // FIXME Without this, waku/router isn't considered to have client
95
- // entries, and "No client entry" error occurs.
96
- // Unless we fix this, RSC-capable packages aren't supported.
97
- // This also seems to cause problems with pnpm.
98
- noExternal: [
99
- "waku"
55
+ noExternal: /^(?!node:)/,
56
+ // FIXME this is very adhoc.
57
+ external: [
58
+ "react",
59
+ "minimatch"
60
+ ]
61
+ },
62
+ resolve: {
63
+ conditions: [
64
+ "react-server"
100
65
  ]
101
66
  },
102
67
  build: {
103
68
  write: false,
104
69
  ssr: true,
105
70
  rollupOptions: {
71
+ onwarn,
106
72
  input: {
107
73
  entries: entriesFile
108
74
  }
109
75
  }
110
76
  }
111
77
  });
112
- const clientEntryFiles = Object.fromEntries(Array.from(clientEntryFileSet).map((fname, i)=>[
113
- `rsc${i}`,
78
+ const clientEntryFiles = Object.fromEntries(await Promise.all(Array.from(clientEntryFileSet).map(async (fname, i)=>[
79
+ `rsc${i}-${await hash(fname)}`,
114
80
  fname
115
- ]));
81
+ ])));
116
82
  const serverEntryFiles = Object.fromEntries(Array.from(serverEntryFileSet).map((fname, i)=>[
117
83
  `rsf${i}`,
118
84
  fname
119
85
  ]));
86
+ return {
87
+ clientEntryFiles,
88
+ serverEntryFiles
89
+ };
90
+ };
91
+ const buildServerBundle = async (config, entriesFile, clientEntryFiles, serverEntryFiles)=>{
120
92
  const serverBuildOutput = await (0, _vite.build)({
121
93
  ..._config.configFileConfig,
122
94
  ssr: {
123
- noExternal: Array.from(clientEntryFileSet).map(// FIXME this might not work with pnpm
95
+ noExternal: Object.values(clientEntryFiles).map(// FIXME this might not work with pnpm
124
96
  (fname)=>_nodepath.default.relative(_nodepath.default.join(config.root, "node_modules"), fname).split("/")[0])
125
97
  },
98
+ resolve: {
99
+ conditions: [
100
+ "react-server"
101
+ ]
102
+ },
126
103
  build: {
127
104
  ssr: true,
128
105
  rollupOptions: {
106
+ onwarn,
129
107
  input: {
130
108
  entries: entriesFile,
131
109
  ...clientEntryFiles,
@@ -135,10 +113,10 @@ async function build() {
135
113
  banner: (chunk)=>{
136
114
  // HACK to bring directives to the front
137
115
  let code = "";
138
- if (chunk.moduleIds.some((id)=>clientEntryFileSet.has(id))) {
116
+ if (chunk.moduleIds.some((id)=>Object.values(clientEntryFiles).includes(id))) {
139
117
  code += '"use client";';
140
118
  }
141
- if (chunk.moduleIds.some((id)=>serverEntryFileSet.has(id))) {
119
+ if (chunk.moduleIds.some((id)=>Object.values(serverEntryFiles).includes(id))) {
142
120
  code += '"use server";';
143
121
  }
144
122
  return code;
@@ -156,43 +134,140 @@ async function build() {
156
134
  if (!("output" in serverBuildOutput)) {
157
135
  throw new Error("Unexpected vite server build output");
158
136
  }
137
+ return serverBuildOutput;
138
+ };
139
+ const buildClientBundle = async (config, clientEntryFiles)=>{
140
+ const indexHtmlFile = _nodepath.default.join(config.root, config.framework.indexHtml);
159
141
  const clientBuildOutput = await (0, _vite.build)({
160
142
  ..._config.configFileConfig,
161
143
  plugins: [
162
- // @ts-ignore
144
+ // @ts-expect-error This expression is not callable.
163
145
  (0, _pluginreact.default)(),
164
- (0, _vitepluginrsc.rscIndexPlugin)()
146
+ (0, _rscindexplugin.rscIndexPlugin)()
165
147
  ],
166
148
  build: {
167
149
  outDir: _nodepath.default.join(config.build.outDir, config.framework.outPublic),
168
150
  rollupOptions: {
151
+ onwarn,
169
152
  input: {
170
153
  main: indexHtmlFile,
171
154
  ...clientEntryFiles
172
155
  },
173
- preserveEntrySignatures: "exports-only"
156
+ preserveEntrySignatures: "exports-only",
157
+ output: {
158
+ entryFileNames: (chunkInfo)=>{
159
+ if (clientEntryFiles[chunkInfo.name]) {
160
+ return "assets/[name].js";
161
+ }
162
+ return "assets/[name]-[hash].js";
163
+ }
164
+ }
174
165
  }
175
166
  }
176
167
  });
177
168
  if (!("output" in clientBuildOutput)) {
178
169
  throw new Error("Unexpected vite client build output");
179
170
  }
180
- const clientEntries = {};
181
- for (const item of clientBuildOutput.output){
182
- const { name , fileName } = item;
183
- const entryFile = name && serverBuildOutput.output.find((item)=>"moduleIds" in item && item.moduleIds.includes(clientEntryFiles[name]))?.fileName;
184
- if (entryFile) {
185
- clientEntries[entryFile] = fileName;
171
+ return clientBuildOutput;
172
+ };
173
+ const emitRscFiles = async (config)=>{
174
+ const pathMap = await (0, _workerapi.getBuilderRSC)();
175
+ const clientModuleMap = new Map();
176
+ const addClientModule = (rscId, serializedProps, id)=>{
177
+ const key = rscId + "/" + serializedProps;
178
+ let idSet = clientModuleMap.get(key);
179
+ if (!idSet) {
180
+ idSet = new Set();
181
+ clientModuleMap.set(key, idSet);
186
182
  }
187
- }
188
- console.log("clientEntries", clientEntries);
189
- _nodefs.default.appendFileSync(distEntriesFile, `export const clientEntries=${JSON.stringify(clientEntries)};`);
190
- const absoluteClientEntries = Object.fromEntries(Object.entries(clientEntries).map(([key, val])=>[
191
- _nodepath.default.join(_nodepath.default.dirname(entriesFile), config.build.outDir, key),
192
- config.base + val
193
- ]));
194
- await (0, _rschandler.setClientEntries)(absoluteClientEntries);
195
- const buildOutput = await (0, _rschandler.buildRSC)();
183
+ idSet.add(id);
184
+ };
185
+ const getClientModules = (rscId, serializedProps)=>{
186
+ const key = rscId + "/" + serializedProps;
187
+ const idSet = clientModuleMap.get(key);
188
+ return Array.from(idSet || []);
189
+ };
190
+ const rscFileSet = new Set(); // XXX could be implemented better
191
+ await Promise.all(Object.entries(pathMap).map(async ([, { elements }])=>{
192
+ for (const [rscId, props] of elements || []){
193
+ // FIXME we blindly expect JSON.stringify usage is deterministic
194
+ const serializedProps = JSON.stringify(props);
195
+ const searchParams = new URLSearchParams();
196
+ searchParams.set("props", serializedProps);
197
+ const destFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, config.framework.rscPrefix + decodeURIComponent(rscId), decodeURIComponent(`${searchParams}`));
198
+ if (!rscFileSet.has(destFile)) {
199
+ rscFileSet.add(destFile);
200
+ _nodefs.default.mkdirSync(_nodepath.default.dirname(destFile), {
201
+ recursive: true
202
+ });
203
+ const pipeable = (0, _workerapi.renderRSC)({
204
+ rscId,
205
+ props
206
+ }, (id)=>addClientModule(rscId, serializedProps, id));
207
+ await new Promise((resolve, reject)=>{
208
+ const stream = _nodefs.default.createWriteStream(destFile);
209
+ stream.on("finish", resolve);
210
+ stream.on("error", reject);
211
+ pipeable.pipe(stream);
212
+ });
213
+ }
214
+ }
215
+ }));
216
+ return {
217
+ pathMap,
218
+ getClientModules,
219
+ rscFiles: Array.from(rscFileSet)
220
+ };
221
+ };
222
+ const emitHtmlFiles = async (config, pathMap, getClientModules)=>{
223
+ const basePrefix = config.base + config.framework.rscPrefix;
224
+ const publicIndexHtmlFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, config.framework.indexHtml);
225
+ const publicIndexHtml = _nodefs.default.readFileSync(publicIndexHtmlFile, {
226
+ encoding: "utf8"
227
+ });
228
+ const htmlFiles = await Promise.all(Object.entries(pathMap).map(async ([pathStr, { elements , customCode }])=>{
229
+ const destFile = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, pathStr, pathStr.endsWith("/") ? "index.html" : "");
230
+ let data = "";
231
+ if (_nodefs.default.existsSync(destFile)) {
232
+ data = _nodefs.default.readFileSync(destFile, {
233
+ encoding: "utf8"
234
+ });
235
+ } else {
236
+ _nodefs.default.mkdirSync(_nodepath.default.dirname(destFile), {
237
+ recursive: true
238
+ });
239
+ data = publicIndexHtml;
240
+ }
241
+ const code = (0, _utils.generatePrefetchCode)(basePrefix, Array.from(elements || []).flatMap(([rscId, props, skipPrefetch])=>{
242
+ if (skipPrefetch) {
243
+ return [];
244
+ }
245
+ return [
246
+ [
247
+ rscId,
248
+ props
249
+ ]
250
+ ];
251
+ }), Array.from(elements || []).flatMap(([rscId, props])=>{
252
+ // FIXME we blindly expect JSON.stringify usage is deterministic
253
+ const serializedProps = JSON.stringify(props);
254
+ return getClientModules(rscId, serializedProps);
255
+ })) + (customCode || "");
256
+ if (code) {
257
+ // HACK is this too naive to inject script code?
258
+ data = data.replace(/<\/body>/, `<script>${code}</script></body>`);
259
+ }
260
+ _nodefs.default.writeFileSync(destFile, data, {
261
+ encoding: "utf8"
262
+ });
263
+ return destFile;
264
+ }));
265
+ return {
266
+ htmlFiles
267
+ };
268
+ };
269
+ const emitPackageJson = (config)=>{
270
+ const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__filename).toString());
196
271
  const origPackageJson = require1(_nodepath.default.join(config.root, "package.json"));
197
272
  const packageJson = {
198
273
  name: origPackageJson.name,
@@ -205,8 +280,72 @@ async function build() {
205
280
  dependencies: origPackageJson.dependencies
206
281
  };
207
282
  _nodefs.default.writeFileSync(_nodepath.default.join(config.root, config.build.outDir, "package.json"), JSON.stringify(packageJson, null, 2));
283
+ };
284
+ const emitVercelOutput = (config, clientBuildOutput, rscFiles, htmlFiles)=>{
285
+ const clientFiles = clientBuildOutput.output.map(({ fileName })=>_nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, fileName));
286
+ const srcDir = _nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic);
287
+ const dstDir = _nodepath.default.join(config.root, config.build.outDir, ".vercel", "output");
288
+ for (const file of [
289
+ ...clientFiles,
290
+ ...rscFiles,
291
+ ...htmlFiles
292
+ ]){
293
+ const dstFile = _nodepath.default.join(dstDir, "static", _nodepath.default.relative(srcDir, file));
294
+ if (!_nodefs.default.existsSync(dstFile)) {
295
+ _nodefs.default.mkdirSync(_nodepath.default.dirname(dstFile), {
296
+ recursive: true
297
+ });
298
+ _nodefs.default.symlinkSync(_nodepath.default.relative(_nodepath.default.dirname(dstFile), file), dstFile);
299
+ }
300
+ }
301
+ const overrides = Object.fromEntries([
302
+ ...rscFiles.filter((file)=>!_nodepath.default.extname(file)).map((file)=>[
303
+ _nodepath.default.relative(srcDir, file),
304
+ {
305
+ contentType: "text/plain"
306
+ }
307
+ ]),
308
+ ...htmlFiles.filter((file)=>!_nodepath.default.extname(file)).map((file)=>[
309
+ _nodepath.default.relative(srcDir, file),
310
+ {
311
+ contentType: "text/html"
312
+ }
313
+ ])
314
+ ]);
315
+ const configJson = {
316
+ version: 3,
317
+ overrides
318
+ };
319
+ _nodefs.default.mkdirSync(dstDir, {
320
+ recursive: true
321
+ });
322
+ _nodefs.default.writeFileSync(_nodepath.default.join(dstDir, "config.json"), JSON.stringify(configJson, null, 2));
323
+ };
324
+ const resolveFileName = (fname)=>{
325
+ for (const ext of [
326
+ ".js",
327
+ ".ts",
328
+ ".tsx",
329
+ ".jsx"
330
+ ]){
331
+ const resolvedName = fname.slice(0, fname.length - _nodepath.default.extname(fname).length) + ext;
332
+ if (_nodefs.default.existsSync(resolvedName)) {
333
+ return resolvedName;
334
+ }
335
+ }
336
+ return fname; // returning the default one
337
+ };
338
+ async function build() {
339
+ const config = await (0, _config.resolveConfig)("build");
340
+ const entriesFile = resolveFileName(_nodepath.default.join(config.root, config.framework.entriesJs));
341
+ const { clientEntryFiles , serverEntryFiles } = await analyzeEntries(entriesFile);
342
+ await buildServerBundle(config, entriesFile, clientEntryFiles, serverEntryFiles);
343
+ const clientBuildOutput = await buildClientBundle(config, clientEntryFiles);
344
+ const { pathMap , getClientModules , rscFiles } = await emitRscFiles(config);
345
+ const { htmlFiles } = await emitHtmlFiles(config, pathMap, getClientModules);
346
+ emitPackageJson(config);
208
347
  // https://vercel.com/docs/build-output-api/v3
209
348
  // So far, only static sites are supported.
210
- createVercelOutput(config, clientBuildOutput.output.map(({ fileName })=>_nodepath.default.join(config.root, config.build.outDir, config.framework.outPublic, fileName)), buildOutput.rscFiles, buildOutput.htmlFiles);
211
- await (0, _rschandler.shutdown)();
349
+ emitVercelOutput(config, clientBuildOutput, rscFiles, htmlFiles);
350
+ await (0, _workerapi.shutdown)();
212
351
  }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "devServer", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return devServer;
9
+ }
10
+ });
11
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
12
+ const _vite = require("vite");
13
+ const _pluginreact = /*#__PURE__*/ _interop_require_default(require("@vitejs/plugin-react"));
14
+ const _config = require("../config.js");
15
+ const _workerapi = require("./rsc/worker-api.js");
16
+ const _rscindexplugin = require("../vite-plugin/rsc-index-plugin.js");
17
+ function _interop_require_default(obj) {
18
+ return obj && obj.__esModule ? obj : {
19
+ default: obj
20
+ };
21
+ }
22
+ function devServer() {
23
+ const vitePromise = (0, _vite.createServer)({
24
+ ..._config.configFileConfig,
25
+ optimizeDeps: {
26
+ include: [
27
+ "react-server-dom-webpack/client"
28
+ ],
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
+ });
44
+ vitePromise.then((vite)=>{
45
+ (0, _workerapi.registerReloadCallback)((type)=>vite.ws.send({
46
+ type
47
+ }));
48
+ });
49
+ return async (req, res, next)=>{
50
+ const vite = await vitePromise;
51
+ if (req.url?.startsWith("/node_modules/")) {
52
+ // HACK re-export "?v=..." URL to avoid dual module hazard.
53
+ const fname = _nodepath.default.join(vite.config.root, req.url);
54
+ for (const item of vite.moduleGraph.idToModuleMap.values()){
55
+ if (item.file === fname && item.url !== req.url) {
56
+ res.setHeader("Content-Type", "application/javascript");
57
+ res.statusCode = 200;
58
+ res.end(`export * from "${item.url}";`, "utf8");
59
+ return;
60
+ }
61
+ }
62
+ }
63
+ vite.middlewares(req, res, next);
64
+ };
65
+ }