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.
- package/README.md +35 -19
- package/dist/cli.js +9 -20
- package/dist/client.d.ts +1 -1
- package/dist/client.js +2 -2
- package/dist/config.d.ts +5 -0
- package/dist/lib/builder/build.js +109 -27
- package/dist/lib/builder/output-netlify.js +3 -13
- package/dist/lib/builder/serve-cloudflare.d.ts +1 -1
- package/dist/lib/builder/serve-cloudflare.js +5 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +1 -0
- package/dist/lib/handlers/dev-worker-api.d.ts +4 -11
- package/dist/lib/handlers/dev-worker-api.js +3 -23
- package/dist/lib/handlers/dev-worker-impl.js +11 -18
- package/dist/lib/handlers/handler-dev.js +3 -10
- package/dist/lib/handlers/handler-prd.js +2 -4
- package/dist/lib/plugins/vite-plugin-rsc-delegate.d.ts +2 -2
- package/dist/lib/plugins/vite-plugin-rsc-delegate.js +57 -17
- package/dist/lib/plugins/vite-plugin-rsc-hmr.d.ts +17 -3
- package/dist/lib/plugins/vite-plugin-rsc-hmr.js +63 -4
- package/dist/lib/plugins/vite-plugin-rsc-serve.d.ts +1 -0
- package/dist/lib/plugins/vite-plugin-rsc-serve.js +10 -0
- package/dist/lib/plugins/vite-plugin-rsc-transform.d.ts +2 -0
- package/dist/lib/plugins/vite-plugin-rsc-transform.js +3 -0
- package/dist/lib/renderers/html-renderer.d.ts +0 -1
- package/dist/lib/renderers/html-renderer.js +1 -1
- package/dist/lib/renderers/rsc-renderer.d.ts +2 -1
- package/dist/lib/renderers/rsc-renderer.js +10 -12
- package/dist/lib/renderers/utils.d.ts +2 -0
- package/dist/lib/renderers/utils.js +11 -0
- package/dist/lib/utils/node-fs.d.ts +1 -0
- package/dist/lib/utils/node-fs.js +1 -0
- package/dist/router/client.d.ts +1 -1
- package/package.json +7 -6
- package/src/cli.ts +5 -21
- package/src/client.ts +3 -3
- package/src/config.ts +5 -0
- package/src/lib/builder/build.ts +123 -32
- package/src/lib/builder/output-netlify.ts +3 -19
- package/src/lib/builder/serve-cloudflare.ts +4 -1
- package/src/lib/config.ts +1 -0
- package/src/lib/handlers/dev-worker-api.ts +6 -32
- package/src/lib/handlers/dev-worker-impl.ts +16 -17
- package/src/lib/handlers/handler-dev.ts +3 -12
- package/src/lib/handlers/handler-prd.ts +0 -2
- package/src/lib/middleware/hono-utils.ts +1 -1
- package/src/lib/plugins/vite-plugin-rsc-delegate.ts +51 -17
- package/src/lib/plugins/vite-plugin-rsc-hmr.ts +89 -8
- package/src/lib/plugins/vite-plugin-rsc-serve.ts +17 -0
- package/src/lib/plugins/vite-plugin-rsc-transform.ts +5 -0
- package/src/lib/renderers/html-renderer.ts +2 -2
- package/src/lib/renderers/rsc-renderer.ts +23 -24
- package/src/lib/renderers/utils.ts +13 -0
- package/src/lib/utils/node-fs.ts +3 -0
- package/dist/lib/plugins/vite-plugin-rsc-reload.d.ts +0 -2
- package/dist/lib/plugins/vite-plugin-rsc-reload.js +0 -43
- 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
|
|
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
|
|
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,
|
|
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 = (
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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('./${
|
|
193
|
+
return import('./${ssrAssetsDir}/${key}.js');
|
|
181
194
|
`).join('')}
|
|
182
|
-
case '${
|
|
183
|
-
return import('./${
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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 '
|
|
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 {
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.js
CHANGED
|
@@ -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 {
|
|
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: '
|
|
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
|
|
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
|
|
46
|
+
export async function registerHotUpdateCallback(fn) {
|
|
47
47
|
const worker = await getWorker();
|
|
48
48
|
const listener = (mesg)=>{
|
|
49
|
-
if (mesg.type === '
|
|
50
|
-
fn(mesg.
|
|
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
|
-
|
|
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: '
|
|
139
|
-
|
|
131
|
+
type: 'hot-update',
|
|
132
|
+
payload
|
|
140
133
|
};
|
|
141
134
|
parentPort.postMessage(mesg);
|
|
142
135
|
})
|