waku 0.19.2 → 0.19.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 (57) hide show
  1. package/README.md +35 -19
  2. package/dist/cli.js +9 -20
  3. package/dist/client.d.ts +1 -1
  4. package/dist/client.js +2 -2
  5. package/dist/config.d.ts +5 -0
  6. package/dist/lib/builder/build.js +109 -27
  7. package/dist/lib/builder/output-netlify.js +3 -13
  8. package/dist/lib/builder/serve-cloudflare.d.ts +1 -1
  9. package/dist/lib/builder/serve-cloudflare.js +5 -1
  10. package/dist/lib/config.d.ts +1 -0
  11. package/dist/lib/config.js +1 -0
  12. package/dist/lib/handlers/dev-worker-api.d.ts +4 -11
  13. package/dist/lib/handlers/dev-worker-api.js +3 -23
  14. package/dist/lib/handlers/dev-worker-impl.js +11 -18
  15. package/dist/lib/handlers/handler-dev.js +3 -10
  16. package/dist/lib/handlers/handler-prd.js +2 -4
  17. package/dist/lib/plugins/vite-plugin-rsc-delegate.d.ts +2 -2
  18. package/dist/lib/plugins/vite-plugin-rsc-delegate.js +57 -17
  19. package/dist/lib/plugins/vite-plugin-rsc-hmr.d.ts +17 -3
  20. package/dist/lib/plugins/vite-plugin-rsc-hmr.js +63 -4
  21. package/dist/lib/plugins/vite-plugin-rsc-serve.d.ts +1 -0
  22. package/dist/lib/plugins/vite-plugin-rsc-serve.js +10 -0
  23. package/dist/lib/plugins/vite-plugin-rsc-transform.d.ts +2 -0
  24. package/dist/lib/plugins/vite-plugin-rsc-transform.js +3 -0
  25. package/dist/lib/renderers/html-renderer.d.ts +0 -1
  26. package/dist/lib/renderers/html-renderer.js +1 -1
  27. package/dist/lib/renderers/rsc-renderer.d.ts +2 -1
  28. package/dist/lib/renderers/rsc-renderer.js +10 -12
  29. package/dist/lib/renderers/utils.d.ts +2 -0
  30. package/dist/lib/renderers/utils.js +11 -0
  31. package/dist/lib/utils/node-fs.d.ts +1 -0
  32. package/dist/lib/utils/node-fs.js +1 -0
  33. package/dist/router/client.d.ts +1 -1
  34. package/package.json +7 -6
  35. package/src/cli.ts +5 -21
  36. package/src/client.ts +3 -3
  37. package/src/config.ts +5 -0
  38. package/src/lib/builder/build.ts +123 -32
  39. package/src/lib/builder/output-netlify.ts +3 -19
  40. package/src/lib/builder/serve-cloudflare.ts +4 -1
  41. package/src/lib/config.ts +1 -0
  42. package/src/lib/handlers/dev-worker-api.ts +6 -32
  43. package/src/lib/handlers/dev-worker-impl.ts +16 -17
  44. package/src/lib/handlers/handler-dev.ts +3 -12
  45. package/src/lib/handlers/handler-prd.ts +0 -2
  46. package/src/lib/middleware/hono-utils.ts +1 -1
  47. package/src/lib/plugins/vite-plugin-rsc-delegate.ts +51 -17
  48. package/src/lib/plugins/vite-plugin-rsc-hmr.ts +89 -8
  49. package/src/lib/plugins/vite-plugin-rsc-serve.ts +17 -0
  50. package/src/lib/plugins/vite-plugin-rsc-transform.ts +5 -0
  51. package/src/lib/renderers/html-renderer.ts +2 -2
  52. package/src/lib/renderers/rsc-renderer.ts +23 -24
  53. package/src/lib/renderers/utils.ts +13 -0
  54. package/src/lib/utils/node-fs.ts +3 -0
  55. package/dist/lib/plugins/vite-plugin-rsc-reload.d.ts +0 -2
  56. package/dist/lib/plugins/vite-plugin-rsc-reload.js +0 -43
  57. package/src/lib/plugins/vite-plugin-rsc-reload.ts +0 -51
package/README.md CHANGED
@@ -31,22 +31,14 @@ npm create waku@latest
31
31
 
32
32
  ## Rendering
33
33
 
34
- While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of composability that are only possible with the advent of React [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
34
+ While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
35
35
 
36
- So please don't be intimidated by the `'use client'` directive! Once you get the hang of it, you'll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time.
36
+ So please don't be intimidated by the `'use client'` directive! Once you get the hang of it, you'll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It's way simpler than maintaining separate codebases for your backend and frontend.
37
37
 
38
- And please don't fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are 100% client components.
38
+ And please don't fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.
39
39
 
40
40
  > Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.
41
41
 
42
- #### Overview
43
-
44
- Each layout and page in Waku is composed of a React component heirarchy.
45
-
46
- It begins with a server component at the top of the tree. Then at points down the heirarchy, you'll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point all imported components are hydrated and will run in the browser as well.
47
-
48
- Server components can still be rendered below this boundary, but only via composition (e.g., `children` props).
49
-
50
42
  #### Server components
51
43
 
52
44
  Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren't included in the client bundle. They have no state, interactivity, or access to browser APIs since they run exclusively on the server.
@@ -127,7 +119,17 @@ export const Providers = ({ children }) => {
127
119
 
128
120
  #### Server-side rendering
129
121
 
130
- Waku provides static prerendering (SSG) or server-side rendering (SSR) options for layouts and pages including both their server and client components.
122
+ SSR is a distinct concept from RSC. Waku provides static prerendering (SSG) or server-side rendering (SSR) options for both layouts and pages including all of their server _and_ client components.
123
+
124
+ #### tl;dr:
125
+
126
+ Each layout and page in Waku is composed of a React component heirarchy.
127
+
128
+ It begins with a server component at the top of the tree. Then at points down the heirarchy, you'll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point in the component heirarchy, all imported components are hydrated and will run in the browser as well.
129
+
130
+ Server components can still be rendered below this boundary, but only via composition (e.g., `children` props). They form [a new layer](https://github.com/reactwg/server-components/discussions/4) that run _before_ any client code.
131
+
132
+ Client components are still server-side rendered. SSR is seperate from RSC. See the [linked diagrams](https://github.com/reactwg/server-components/discussions/4) for a helpful visual.
131
133
 
132
134
  #### Further reading
133
135
 
@@ -647,6 +649,27 @@ Adding the `--with-vercel-static` flag to the build script will produce static s
647
649
  }
648
650
  ```
649
651
 
652
+ ### Netlify
653
+
654
+ Waku projects can be deployed to Netlify with the [Netlify CLI](https://docs.netlify.com/cli/get-started/).
655
+
656
+ ```
657
+ npm run build -- --with-netlify
658
+ netlify deploy --dir=dist/public
659
+ ```
660
+
661
+ #### Pure SSG
662
+
663
+ Adding the `--with-netlify-static` flag to the build script will produce static sites without Netlify functions.
664
+
665
+ ```
666
+ {
667
+ "scripts": {
668
+ "build": "waku build --with-ssr --with-netlify-static"
669
+ }
670
+ }
671
+ ```
672
+
650
673
  ### Cloudflare (experimental)
651
674
 
652
675
  ```
@@ -661,13 +684,6 @@ npm run build -- --with-deno
661
684
  DENO_DEPLOY_TOKEN=... deployctl deploy --project=... --prod dist/serve.js --exclude node_modules
662
685
  ```
663
686
 
664
- ### Netlify Deploy (experimental)
665
-
666
- ```
667
- npm run build -- --with-netlify
668
- netlify deploy
669
- ```
670
-
671
687
  ### AWS Lambda (experimental)
672
688
 
673
689
  ```
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
2
+ import { existsSync, writeFileSync, unlinkSync } from 'node:fs';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { parseArgs } from 'node:util';
5
5
  import { createRequire } from 'node:module';
@@ -8,11 +8,18 @@ import { Hono } from 'hono';
8
8
  import { serve } from '@hono/node-server';
9
9
  import { serveStatic } from '@hono/node-server/serve-static';
10
10
  import * as swc from '@swc/core';
11
+ import * as dotenv from 'dotenv';
11
12
  import { resolveConfig } from './lib/config.js';
12
13
  import { honoMiddleware as honoDevMiddleware } from './lib/middleware/hono-dev.js';
13
14
  import { honoMiddleware as honoPrdMiddleware } from './lib/middleware/hono-prd.js';
14
15
  import { build } from './lib/builder/build.js';
15
16
  const require = createRequire(new URL('.', import.meta.url));
17
+ dotenv.config({
18
+ path: [
19
+ '.env.local',
20
+ '.env'
21
+ ]
22
+ });
16
23
  const { values, positionals } = parseArgs({
17
24
  args: process.argv.slice(2),
18
25
  allowPositionals: true,
@@ -51,7 +58,6 @@ const { values, positionals } = parseArgs({
51
58
  }
52
59
  }
53
60
  });
54
- loadEnv();
55
61
  const config = await loadConfig();
56
62
  const cmd = positionals[0];
57
63
  if (values.version) {
@@ -100,7 +106,7 @@ async function runBuild(options) {
100
106
  ...options,
101
107
  config,
102
108
  env: process.env,
103
- deploy: (values['with-vercel'] ?? !!process.env.VERCEL ? values['with-vercel-static'] ? 'vercel-static' : 'vercel-serverless' : undefined) || (values['with-cloudflare'] ? 'cloudflare' : undefined) || (values['with-deno'] ? 'deno' : undefined) || (values['with-netlify'] ? values['with-netlify-static'] ? 'netlify-static' : 'netlify-functions' : undefined) || (values['with-aws-lambda'] ? 'aws-lambda' : undefined)
109
+ deploy: (values['with-vercel'] ?? !!process.env.VERCEL ? values['with-vercel-static'] ? 'vercel-static' : 'vercel-serverless' : undefined) || (values['with-cloudflare'] ? 'cloudflare' : undefined) || (values['with-deno'] ? 'deno' : undefined) || (values['with-netlify'] ?? !!process.env.NETLIFY ? values['with-netlify-static'] ? 'netlify-static' : 'netlify-functions' : undefined) || (values['with-aws-lambda'] ? 'aws-lambda' : undefined)
104
110
  });
105
111
  }
106
112
  async function runStart(options) {
@@ -155,23 +161,6 @@ Options:
155
161
  -h, --help Display this help message
156
162
  `);
157
163
  }
158
- // TODO consider using a library such as `dotenv`
159
- function loadEnv() {
160
- if (existsSync('.env.local')) {
161
- for (const line of readFileSync('.env.local', 'utf8').split('\n')){
162
- const [key, value] = line.split('=');
163
- if (key && value) {
164
- if (value.startsWith('"') && value.endsWith('"')) {
165
- process.env[key.trim()] = value.slice(1, -1);
166
- } else if (value.startsWith("'") && value.endsWith("'")) {
167
- process.env[key.trim()] = value.slice(1, -1);
168
- } else {
169
- process.env[key.trim()] = value.trim();
170
- }
171
- }
172
- }
173
- }
174
- }
175
164
  // TODO is this a good idea?
176
165
  async function loadConfig() {
177
166
  if (!existsSync('waku.config.ts')) {
package/dist/client.d.ts CHANGED
@@ -5,7 +5,7 @@ declare global {
5
5
  }
6
6
  }
7
7
  type Elements = Promise<Record<string, ReactNode>>;
8
- type SetElements = (fn: (prev: Elements) => Elements) => void;
8
+ type SetElements = (updater: Elements | ((prev: Elements) => Elements)) => void;
9
9
  type CacheEntry = [
10
10
  input: string,
11
11
  searchParamsString: string,
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  'use client';
3
3
  import { createContext, createElement, memo, use, useCallback, useState, startTransition } from 'react';
4
4
  import RSDWClient from 'react-server-dom-webpack/client';
5
- import { encodeInput } from './lib/renderers/utils.js';
5
+ import { encodeInput, encodeActionId } from './lib/renderers/utils.js';
6
6
  const { createFromFetch, encodeReply } = RSDWClient;
7
7
  const BASE_PATH = `${import.meta.env?.WAKU_CONFIG_BASE_PATH}${import.meta.env?.WAKU_CONFIG_RSC_PATH}/`;
8
8
  const checkStatus = async (responsePromise)=>{
@@ -37,7 +37,7 @@ export const fetchRSC = (input, searchParamsString, setElements, cache = fetchCa
37
37
  }
38
38
  const options = {
39
39
  async callServer (actionId, args) {
40
- const response = fetch(BASE_PATH + encodeInput(encodeURIComponent(actionId)), {
40
+ const response = fetch(BASE_PATH + encodeInput(encodeActionId(actionId)), {
41
41
  method: 'POST',
42
42
  body: await encodeReply(args)
43
43
  });
package/dist/config.d.ts CHANGED
@@ -27,6 +27,11 @@ export interface Config {
27
27
  * Defaults to "assets".
28
28
  */
29
29
  assetsDir?: string;
30
+ /**
31
+ * The SSR directory relative to distDir.
32
+ * Defaults to "ssr".
33
+ */
34
+ ssrDir?: string;
30
35
  /**
31
36
  * The index.html file for any directories.
32
37
  * Defaults to "index.html".
@@ -5,7 +5,7 @@ import { build as buildVite, resolveConfig as resolveViteConfig } from 'vite';
5
5
  import viteReact from '@vitejs/plugin-react';
6
6
  import { resolveConfig } from '../config.js';
7
7
  import { joinPath, extname, filePathToFileURL, fileURLToFilePath, decodeFilePathFromAbsolute } from '../utils/path.js';
8
- import { createReadStream, createWriteStream, existsSync, rename, mkdir, readFile, writeFile, appendFile, unlink } from '../utils/node-fs.js';
8
+ import { createReadStream, createWriteStream, existsSync, copyFile, rename, mkdir, readFile, writeFile, appendFile, unlink } from '../utils/node-fs.js';
9
9
  import { encodeInput, generatePrefetchCode } from '../renderers/utils.js';
10
10
  import { RSDW_SERVER_MODULE, RSDW_SERVER_MODULE_VALUE, renderRsc, getBuildConfig, getSsrConfig } from '../renderers/rsc-renderer.js';
11
11
  import { renderHtml } from '../renderers/html-renderer.js';
@@ -51,6 +51,7 @@ const analyzeEntries = async (entriesFile)=>{
51
51
  rscAnalyzePlugin(commonFileSet, clientFileSet, serverFileSet)
52
52
  ],
53
53
  ssr: {
54
+ target: 'webworker',
54
55
  resolve: {
55
56
  conditions: [
56
57
  'react-server',
@@ -92,18 +93,17 @@ const analyzeEntries = async (entriesFile)=>{
92
93
  serverEntryFiles
93
94
  };
94
95
  };
95
- const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, ssr, serve)=>{
96
+ // For RSC
97
+ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, ssr, serve, isNodeCompatible)=>{
96
98
  const serverBuildOutput = await buildVite({
97
99
  plugins: [
98
100
  nonjsResolvePlugin(),
99
101
  rscTransformPlugin({
100
102
  isBuild: true,
101
103
  assetsDir: config.assetsDir,
102
- clientEntryFiles: {
103
- // FIXME this seems very ad-hoc
104
- [WAKU_CLIENT]: decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../../client.js')),
105
- ...clientEntryFiles
106
- },
104
+ wakuClientId: WAKU_CLIENT,
105
+ wakuClientPath: decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../../client.js')),
106
+ clientEntryFiles,
107
107
  serverEntryFiles
108
108
  }),
109
109
  rscEnvPlugin({
@@ -114,11 +114,12 @@ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile,
114
114
  ...config,
115
115
  entriesFile,
116
116
  srcServeFile: decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), `../serve-${serve}.js`)),
117
- ssr
117
+ ssr,
118
+ serve
118
119
  })
119
120
  ] : []
120
121
  ],
121
- ssr: {
122
+ ssr: isNodeCompatible ? {
122
123
  resolve: {
123
124
  conditions: [
124
125
  'react-server',
@@ -129,11 +130,22 @@ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile,
129
130
  'workerd'
130
131
  ]
131
132
  },
132
- external: serve === 'cloudflare' && [
133
- 'hono',
134
- 'hono/cloudflare-workers'
135
- ] || [],
136
133
  noExternal: /^(?!node:)/
134
+ } : {
135
+ target: 'webworker',
136
+ resolve: {
137
+ conditions: [
138
+ 'react-server',
139
+ 'workerd',
140
+ 'worker'
141
+ ],
142
+ externalConditions: [
143
+ 'react-server',
144
+ 'workerd',
145
+ 'worker'
146
+ ]
147
+ },
148
+ noExternal: true
137
149
  },
138
150
  define: {
139
151
  'process.env.NODE_ENV': JSON.stringify('production')
@@ -169,7 +181,8 @@ const buildServerBundle = async (rootDir, config, entriesFile, distEntriesFile,
169
181
  if (!('output' in serverBuildOutput)) {
170
182
  throw new Error('Unexpected vite server build output');
171
183
  }
172
- const psDir = joinPath(config.publicDir, config.assetsDir);
184
+ // TODO If ssr === false, we don't need to write ssr entries.
185
+ const ssrAssetsDir = joinPath(config.ssrDir, config.assetsDir);
173
186
  const code = `
174
187
  export function loadModule(id) {
175
188
  switch (id) {
@@ -177,16 +190,16 @@ export function loadModule(id) {
177
190
  return import('./${RSDW_SERVER_MODULE}.js');
178
191
  ${Object.keys(CLIENT_MODULE_MAP).map((key)=>`
179
192
  case '${CLIENT_PREFIX}${key}':
180
- return import('./${psDir}/${key}.js');
193
+ return import('./${ssrAssetsDir}/${key}.js');
181
194
  `).join('')}
182
- case '${psDir}/${WAKU_CLIENT}.js':
183
- return import('./${psDir}/${WAKU_CLIENT}.js');
195
+ case '${ssrAssetsDir}/${WAKU_CLIENT}.js':
196
+ return import('./${ssrAssetsDir}/${WAKU_CLIENT}.js');
197
+ ${Object.entries(clientEntryFiles || {}).map(([k])=>`
198
+ case '${ssrAssetsDir}/${k}.js':
199
+ return import('./${ssrAssetsDir}/${k}.js');`).join('')}
184
200
  ${Object.entries(serverEntryFiles || {}).map(([k])=>`
185
201
  case '${config.assetsDir}/${k}.js':
186
202
  return import('./${config.assetsDir}/${k}.js');`).join('')}
187
- ${Object.entries(clientEntryFiles || {}).map(([k])=>`
188
- case '${psDir}/${k}.js':
189
- return import('./${psDir}/${k}.js');`).join('')}
190
203
  default:
191
204
  throw new Error('Cannot find module: ' + id);
192
205
  }
@@ -195,6 +208,71 @@ ${Object.entries(clientEntryFiles || {}).map(([k])=>`
195
208
  await appendFile(distEntriesFile, code);
196
209
  return serverBuildOutput;
197
210
  };
211
+ // For SSR (render client components on server to generate HTML)
212
+ const buildSsrBundle = async (rootDir, config, commonEntryFiles, clientEntryFiles, serverBuildOutput, isNodeCompatible)=>{
213
+ const mainJsFile = joinPath(rootDir, config.srcDir, config.mainJs);
214
+ const cssAssets = serverBuildOutput.output.flatMap(({ type, fileName })=>type === 'asset' && fileName.endsWith('.css') ? [
215
+ fileName
216
+ ] : []);
217
+ await buildVite({
218
+ base: config.basePath,
219
+ plugins: [
220
+ rscIndexPlugin({
221
+ ...config,
222
+ cssAssets
223
+ }),
224
+ rscEnvPlugin({
225
+ config,
226
+ hydrate: true
227
+ })
228
+ ],
229
+ ssr: isNodeCompatible ? {
230
+ noExternal: /^(?!node:)/
231
+ } : {
232
+ target: 'webworker',
233
+ resolve: {
234
+ conditions: [
235
+ 'worker'
236
+ ],
237
+ externalConditions: [
238
+ 'worker'
239
+ ]
240
+ },
241
+ noExternal: true
242
+ },
243
+ define: {
244
+ 'process.env.NODE_ENV': JSON.stringify('production')
245
+ },
246
+ publicDir: false,
247
+ build: {
248
+ ssr: true,
249
+ outDir: joinPath(rootDir, config.distDir, config.ssrDir),
250
+ rollupOptions: {
251
+ onwarn,
252
+ input: {
253
+ main: mainJsFile,
254
+ ...CLIENT_MODULE_MAP,
255
+ ...commonEntryFiles,
256
+ ...clientEntryFiles
257
+ },
258
+ output: {
259
+ entryFileNames: (chunkInfo)=>{
260
+ if (CLIENT_MODULE_MAP[chunkInfo.name] || commonEntryFiles[chunkInfo.name] || clientEntryFiles[chunkInfo.name]) {
261
+ return config.assetsDir + '/[name].js';
262
+ }
263
+ return config.assetsDir + '/[name]-[hash].js';
264
+ }
265
+ }
266
+ }
267
+ }
268
+ });
269
+ for (const cssAsset of cssAssets){
270
+ const from = joinPath(rootDir, config.distDir, cssAsset);
271
+ const to = joinPath(rootDir, config.distDir, config.ssrDir, cssAsset);
272
+ await copyFile(from, to);
273
+ }
274
+ };
275
+ // For Browsers
198
276
  const buildClientBundle = async (rootDir, config, commonEntryFiles, clientEntryFiles, serverBuildOutput, ssr)=>{
199
277
  const mainJsFile = joinPath(rootDir, config.srcDir, config.mainJs);
200
278
  const cssAssets = serverBuildOutput.output.flatMap(({ type, fileName })=>type === 'asset' && fileName.endsWith('.css') ? [
@@ -219,14 +297,16 @@ const buildClientBundle = async (rootDir, config, commonEntryFiles, clientEntryF
219
297
  onwarn,
220
298
  input: {
221
299
  main: mainJsFile,
222
- ...CLIENT_MODULE_MAP,
300
+ [WAKU_CLIENT]: CLIENT_MODULE_MAP[WAKU_CLIENT],
223
301
  ...commonEntryFiles,
224
302
  ...clientEntryFiles
225
303
  },
226
304
  preserveEntrySignatures: 'exports-only',
227
305
  output: {
228
306
  entryFileNames: (chunkInfo)=>{
229
- if (CLIENT_MODULE_MAP[chunkInfo.name] || commonEntryFiles[chunkInfo.name] || clientEntryFiles[chunkInfo.name]) {
307
+ if ([
308
+ WAKU_CLIENT
309
+ ].includes(chunkInfo.name) || commonEntryFiles[chunkInfo.name] || clientEntryFiles[chunkInfo.name]) {
230
310
  return config.assetsDir + '/[name].js';
231
311
  }
232
312
  return config.assetsDir + '/[name]-[hash].js';
@@ -356,13 +436,11 @@ const emitHtmlFiles = async (rootDir, config, distEntriesFile, distEntries, buil
356
436
  pathname,
357
437
  searchParams,
358
438
  isDev: false,
359
- entries: distEntries,
360
- isBuild: true
439
+ entries: distEntries
361
440
  }),
362
441
  loadClientModule: (key)=>distEntries.loadModule(CLIENT_PREFIX + key),
363
442
  isDev: false,
364
- loadModule: distEntries.loadModule,
365
- isBuild: true
443
+ loadModule: distEntries.loadModule
366
444
  });
367
445
  await mkdir(joinPath(destHtmlFile, '..'), {
368
446
  recursive: true
@@ -399,8 +477,12 @@ export async function build(options) {
399
477
  const rootDir = (await resolveViteConfig({}, 'build', 'production', 'production')).root;
400
478
  const entriesFile = resolveFileName(joinPath(rootDir, config.srcDir, config.entriesJs));
401
479
  const distEntriesFile = resolveFileName(joinPath(rootDir, config.distDir, config.entriesJs));
480
+ const isNodeCompatible = options.deploy !== 'cloudflare' && options.deploy !== 'deno';
402
481
  const { commonEntryFiles, clientEntryFiles, serverEntryFiles } = await analyzeEntries(entriesFile);
403
- const serverBuildOutput = await buildServerBundle(rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, !!options.ssr, (options.deploy === 'vercel-serverless' ? 'vercel' : false) || (options.deploy === 'cloudflare' ? 'cloudflare' : false) || (options.deploy === 'deno' ? 'deno' : false) || (options.deploy === 'netlify-functions' ? 'netlify' : false) || (options.deploy === 'aws-lambda' ? 'aws-lambda' : false));
482
+ const serverBuildOutput = await buildServerBundle(rootDir, config, entriesFile, distEntriesFile, commonEntryFiles, clientEntryFiles, serverEntryFiles, !!options.ssr, (options.deploy === 'vercel-serverless' ? 'vercel' : false) || (options.deploy === 'cloudflare' ? 'cloudflare' : false) || (options.deploy === 'deno' ? 'deno' : false) || (options.deploy === 'netlify-functions' ? 'netlify' : false) || (options.deploy === 'aws-lambda' ? 'aws-lambda' : false), isNodeCompatible);
483
+ if (options.ssr) {
484
+ await buildSsrBundle(rootDir, config, commonEntryFiles, clientEntryFiles, serverBuildOutput, isNodeCompatible);
485
+ }
404
486
  await buildClientBundle(rootDir, config, commonEntryFiles, clientEntryFiles, serverBuildOutput, !!options.ssr);
405
487
  const distEntries = await import(filePathToFileURL(distEntriesFile));
406
488
  const buildConfig = await getBuildConfig({
@@ -1,27 +1,17 @@
1
1
  import path from 'node:path';
2
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
3
  export const emitNetlifyOutput = async (rootDir, config, type)=>{
4
4
  if (type === 'functions') {
5
- const functionsDir = path.join(rootDir, 'functions');
5
+ const functionsDir = path.join(rootDir, 'netlify/functions');
6
6
  mkdirSync(functionsDir, {
7
7
  recursive: true
8
8
  });
9
9
  writeFileSync(path.join(functionsDir, 'serve.js'), `
10
- export { default } from '../${config.distDir}/${config.serveJs}';
10
+ export { default } from '../../${config.distDir}/${config.serveJs}';
11
11
  export const config = {
12
12
  preferStatic: true,
13
13
  path: ['/', '/*'],
14
14
  };
15
15
  `);
16
16
  }
17
- const netlifyTomlFile = path.join(rootDir, 'netlify.toml');
18
- if (!existsSync(netlifyTomlFile)) {
19
- writeFileSync(netlifyTomlFile, `
20
- [build]
21
- publish = "${config.distDir}/${config.publicDir}"
22
- ` + (type === 'functions' ? `
23
- [functions]
24
- directory = "functions"
25
- ` : ''));
26
- }
27
17
  };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- declare const app: Hono<import("hono").Env, {}, "/">;
2
+ declare const app: Hono<import("hono").Env, import("hono/types").BlankSchema, "/">;
3
3
  declare const _default: {
4
4
  fetch(request: Request, env: Record<string, string>, ctx: Parameters<typeof app.fetch>[2]): Promise<Response>;
5
5
  };
@@ -1,12 +1,16 @@
1
1
  import { Hono } from 'hono';
2
2
  import { serveStatic } from 'hono/cloudflare-workers';
3
+ // @ts-expect-error no types
4
+ // eslint-disable-next-line import/no-unresolved
5
+ import manifest from '__STATIC_CONTENT_MANIFEST';
3
6
  import { honoMiddleware } from '../middleware/hono-prd.js';
4
7
  const ssr = !!import.meta.env.WAKU_BUILD_SSR;
5
8
  const loadEntries = ()=>import(import.meta.env.WAKU_ENTRIES_FILE);
6
9
  let serveWaku;
7
10
  const app = new Hono();
8
11
  app.use('*', serveStatic({
9
- root: './'
12
+ root: './',
13
+ manifest
10
14
  }));
11
15
  app.use('*', (c, next)=>serveWaku(c, next));
12
16
  export default {
@@ -9,6 +9,7 @@ export declare function resolveConfig(config: Config): Promise<{
9
9
  distDir: string;
10
10
  publicDir: string;
11
11
  assetsDir: string;
12
+ ssrDir: string;
12
13
  indexHtml: string;
13
14
  mainJs: string;
14
15
  entriesJs: string;
@@ -10,6 +10,7 @@ export async function resolveConfig(config) {
10
10
  distDir: 'dist',
11
11
  publicDir: 'public',
12
12
  assetsDir: 'assets',
13
+ ssrDir: 'ssr',
13
14
  indexHtml: 'index.html',
14
15
  mainJs: 'main.tsx',
15
16
  entriesJs: 'entries.js',
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type { Worker as WorkerType } from 'node:worker_threads';
3
3
  import type { ResolvedConfig } from '../config.js';
4
- import type { ModuleImportResult } from '../plugins/vite-plugin-rsc-hmr.js';
4
+ import type { HotUpdatePayload } from '../plugins/vite-plugin-rsc-hmr.js';
5
5
  export type RenderRequest = {
6
6
  input: string;
7
7
  searchParamsString: string;
@@ -28,13 +28,8 @@ export type MessageReq = ({
28
28
  searchParamsString: string;
29
29
  };
30
30
  export type MessageRes = {
31
- type: 'full-reload';
32
- } | {
33
- type: 'hot-import';
34
- source: string;
35
- } | {
36
- type: 'module-import';
37
- result: ModuleImportResult;
31
+ type: 'hot-update';
32
+ payload: HotUpdatePayload;
38
33
  } | {
39
34
  id: number;
40
35
  type: 'start';
@@ -60,9 +55,7 @@ export type MessageRes = {
60
55
  type: 'noSsrConfig';
61
56
  };
62
57
  export declare function initializeWorker(config: ResolvedConfig): void;
63
- export declare function registerReloadCallback(fn: (type: 'full-reload') => void): Promise<() => WorkerType>;
64
- export declare function registerImportCallback(fn: (source: string) => void): Promise<() => WorkerType>;
65
- export declare function registerModuleCallback(fn: (result: ModuleImportResult) => void): Promise<() => WorkerType>;
58
+ export declare function registerHotUpdateCallback(fn: (payload: HotUpdatePayload) => void): Promise<() => WorkerType>;
66
59
  export declare function renderRscWithWorker<Context>(rr: RenderRequest): Promise<readonly [ReadableStream, Context]>;
67
60
  export declare function getSsrConfigWithWorker(config: ResolvedConfig, pathname: string, searchParams: URLSearchParams): Promise<{
68
61
  input: string;
@@ -43,31 +43,11 @@ const getWorker = ()=>{
43
43
  }
44
44
  return workerPromise;
45
45
  };
46
- export async function registerReloadCallback(fn) {
46
+ export async function registerHotUpdateCallback(fn) {
47
47
  const worker = await getWorker();
48
48
  const listener = (mesg)=>{
49
- if (mesg.type === 'full-reload') {
50
- fn(mesg.type);
51
- }
52
- };
53
- worker.on('message', listener);
54
- return ()=>worker.off('message', listener);
55
- }
56
- export async function registerImportCallback(fn) {
57
- const worker = await getWorker();
58
- const listener = (mesg)=>{
59
- if (mesg.type === 'hot-import') {
60
- fn(mesg.source);
61
- }
62
- };
63
- worker.on('message', listener);
64
- return ()=>worker.off('message', listener);
65
- }
66
- export async function registerModuleCallback(fn) {
67
- const worker = await getWorker();
68
- const listener = (mesg)=>{
69
- if (mesg.type === 'module-import') {
70
- fn(mesg.result);
49
+ if (mesg.type === 'hot-update') {
50
+ fn(mesg.payload);
71
51
  }
72
52
  };
73
53
  worker.on('message', listener);
@@ -4,13 +4,12 @@ import { parentPort, getEnvironmentData } from 'node:worker_threads';
4
4
  import { Server } from 'node:http';
5
5
  import { createServer as createViteServer } from 'vite';
6
6
  import viteReact from '@vitejs/plugin-react';
7
- import { joinPath, fileURLToFilePath } from '../utils/path.js';
7
+ import { joinPath, fileURLToFilePath, encodeFilePathToAbsolute } from '../utils/path.js';
8
8
  import { deepFreeze, hasStatusCode } from '../renderers/utils.js';
9
9
  import { renderRsc, getSsrConfig } from '../renderers/rsc-renderer.js';
10
10
  import { nonjsResolvePlugin } from '../plugins/vite-plugin-nonjs-resolve.js';
11
11
  import { rscTransformPlugin } from '../plugins/vite-plugin-rsc-transform.js';
12
12
  import { rscEnvPlugin } from '../plugins/vite-plugin-rsc-env.js';
13
- import { rscReloadPlugin } from '../plugins/vite-plugin-rsc-reload.js';
14
13
  import { rscDelegatePlugin } from '../plugins/vite-plugin-rsc-delegate.js';
15
14
  import { mergeUserViteConfig } from '../utils/merge-vite-config.js';
16
15
  const { default: module } = await import('node:module');
@@ -21,6 +20,11 @@ if (HAS_MODULE_REGISTER) {
21
20
  globalThis.__WAKU_PRIVATE_ENV__ = getEnvironmentData('__WAKU_PRIVATE_ENV__');
22
21
  const configSrcDir = getEnvironmentData('CONFIG_SRC_DIR');
23
22
  const configEntriesJs = getEnvironmentData('CONFIG_ENTRIES_JS');
23
+ const resolveClientEntryForDev = (id, config)=>{
24
+ const filePath = id.startsWith('file://') ? fileURLToFilePath(id) : id;
25
+ // HACK this relies on Vite's internal implementation detail.
26
+ return config.basePath + '@fs' + encodeFilePathToAbsolute(filePath);
27
+ };
24
28
  const handleRender = async (mesg)=>{
25
29
  const { id, type: _removed, hasModuleIdCallback, ...rest } = mesg;
26
30
  const rr = rest;
@@ -46,6 +50,7 @@ const handleRender = async (mesg)=>{
46
50
  moduleIdCallback: rr.moduleIdCallback,
47
51
  isDev: true,
48
52
  customImport: loadServerFile,
53
+ resolveClientEntry: (id)=>resolveClientEntryForDev(id, rr.config),
49
54
  entries: await loadEntries(rr.config)
50
55
  });
51
56
  const mesg = {
@@ -79,6 +84,7 @@ const handleGetSsrConfig = async (mesg)=>{
79
84
  pathname,
80
85
  searchParams,
81
86
  isDev: true,
87
+ resolveClientEntry: (id)=>resolveClientEntryForDev(id, config),
82
88
  entries: await loadEntries(config)
83
89
  });
84
90
  const mesg = ssrConfig ? {
@@ -105,7 +111,6 @@ const handleGetSsrConfig = async (mesg)=>{
105
111
  }
106
112
  };
107
113
  const dummyServer = new Server(); // FIXME we hope to avoid this hack
108
- const moduleImports = new Set();
109
114
  const mergedViteConfig = await mergeUserViteConfig({
110
115
  plugins: [
111
116
  viteReact(),
@@ -121,22 +126,10 @@ const mergedViteConfig = await mergeUserViteConfig({
121
126
  rscTransformPlugin({
122
127
  isBuild: false
123
128
  }),
124
- rscReloadPlugin(moduleImports, (type)=>{
125
- const mesg = {
126
- type
127
- };
128
- parentPort.postMessage(mesg);
129
- }),
130
- rscDelegatePlugin(moduleImports, (source)=>{
131
- const mesg = {
132
- type: 'hot-import',
133
- source
134
- };
135
- parentPort.postMessage(mesg);
136
- }, (result)=>{
129
+ rscDelegatePlugin((payload)=>{
137
130
  const mesg = {
138
- type: 'module-import',
139
- result
131
+ type: 'hot-update',
132
+ payload
140
133
  };
141
134
  parentPort.postMessage(mesg);
142
135
  })