waku 0.21.1 → 0.21.2

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 (38) hide show
  1. package/README.md +1 -1
  2. package/dist/client.js +4 -8
  3. package/dist/lib/builder/build.js +102 -33
  4. package/dist/lib/middleware/dev-server-impl.js +4 -2
  5. package/dist/lib/middleware/rsc.js +1 -1
  6. package/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts +5 -0
  7. package/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js +52 -0
  8. package/dist/lib/plugins/vite-plugin-deploy-cloudflare.d.ts +7 -0
  9. package/dist/lib/plugins/vite-plugin-deploy-cloudflare.js +143 -0
  10. package/dist/lib/plugins/vite-plugin-deploy-deno.d.ts +5 -0
  11. package/dist/lib/plugins/{vite-plugin-rsc-serve.js → vite-plugin-deploy-deno.js} +14 -16
  12. package/dist/lib/plugins/vite-plugin-deploy-netlify.d.ts +6 -0
  13. package/dist/lib/plugins/vite-plugin-deploy-netlify.js +78 -0
  14. package/dist/lib/plugins/vite-plugin-deploy-partykit.d.ts +5 -0
  15. package/dist/lib/plugins/vite-plugin-deploy-partykit.js +61 -0
  16. package/dist/lib/plugins/vite-plugin-deploy-vercel.d.ts +8 -0
  17. package/dist/lib/plugins/vite-plugin-deploy-vercel.js +101 -0
  18. package/dist/lib/renderers/html-renderer.js +2 -2
  19. package/dist/lib/renderers/rsc-renderer.js +12 -8
  20. package/dist/router/create-pages.d.ts +0 -4
  21. package/dist/router/create-pages.js +7 -17
  22. package/dist/router/define-router.d.ts +0 -3
  23. package/dist/router/define-router.js +21 -20
  24. package/dist/router/fs-router.js +7 -7
  25. package/dist/server.d.ts +10 -5
  26. package/dist/server.js +5 -0
  27. package/package.json +7 -7
  28. package/dist/lib/builder/output-aws-lambda.d.ts +0 -2
  29. package/dist/lib/builder/output-aws-lambda.js +0 -7
  30. package/dist/lib/builder/output-cloudflare.d.ts +0 -2
  31. package/dist/lib/builder/output-cloudflare.js +0 -78
  32. package/dist/lib/builder/output-netlify.d.ts +0 -2
  33. package/dist/lib/builder/output-netlify.js +0 -31
  34. package/dist/lib/builder/output-partykit.d.ts +0 -2
  35. package/dist/lib/builder/output-partykit.js +0 -15
  36. package/dist/lib/builder/output-vercel.d.ts +0 -2
  37. package/dist/lib/builder/output-vercel.js +0 -53
  38. package/dist/lib/plugins/vite-plugin-rsc-serve.d.ts +0 -9
package/README.md CHANGED
@@ -1007,7 +1007,7 @@ Note: When rendering in static mode, please be sure to return `render: 'static'`
1007
1007
 
1008
1008
  ```
1009
1009
  npm run build -- --with-cloudflare
1010
- npx wrangler dev # or deploy
1010
+ npx wrangler pages dev # or deploy
1011
1011
  ```
1012
1012
 
1013
1013
  ### PartyKit (experimental)
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react/canary" />
2
2
  'use client';
3
- import { Component, createContext, createElement, memo, use, useCallback, useEffect, useState, startTransition } from 'react';
3
+ import { Component, createContext, createElement, memo, use, useCallback, useEffect, useState } from 'react';
4
4
  import RSDWClient from 'react-server-dom-webpack/client';
5
5
  import { encodeInput, encodeActionId } from './lib/renderers/utils.js';
6
6
  const { createFromFetch, encodeReply } = RSDWClient;
@@ -62,10 +62,8 @@ const defaultFetchCache = {};
62
62
  callServer: (actionId, args)=>callServerRSC(actionId, args, fetchCache)
63
63
  });
64
64
  fetchCache[ON_FETCH_DATA]?.(data);
65
- startTransition(()=>{
66
- // FIXME this causes rerenders even if data is empty
67
- fetchCache[SET_ELEMENTS]?.((prev)=>mergeElements(prev, data));
68
- });
65
+ // FIXME this causes rerenders even if data is empty
66
+ fetchCache[SET_ELEMENTS]?.((prev)=>mergeElements(prev, data));
69
67
  return (await data)._value;
70
68
  };
71
69
  const prefetchedParams = new WeakMap();
@@ -126,9 +124,7 @@ export const Root = ({ initialInput, initialParams, fetchCache = defaultFetchCac
126
124
  // clear cache entry before fetching
127
125
  delete fetchCache[ENTRY];
128
126
  const data = fetchRSC(input, params, fetchCache);
129
- startTransition(()=>{
130
- setElements((prev)=>mergeElements(prev, data));
131
- });
127
+ setElements((prev)=>mergeElements(prev, data));
132
128
  }, [
133
129
  fetchCache
134
130
  ]);
@@ -2,8 +2,9 @@ import { Readable } from 'node:stream';
2
2
  import { pipeline } from 'node:stream/promises';
3
3
  import { build as buildVite, resolveConfig as resolveViteConfig } from 'vite';
4
4
  import viteReact from '@vitejs/plugin-react';
5
+ import { unstable_getPlatformObject } from '../../server.js';
5
6
  import { resolveConfig, EXTENSIONS } from '../config.js';
6
- import { decodeFilePathFromAbsolute, extname, filePathToFileURL, fileURLToFilePath, joinPath } from '../utils/path.js';
7
+ import { decodeFilePathFromAbsolute, extname, filePathToFileURL, fileURLToFilePath, getPathMapping, joinPath } from '../utils/path.js';
7
8
  import { appendFile, createWriteStream, existsSync, mkdir, readdir, readFile, rename, unlink, writeFile } from '../utils/node-fs.js';
8
9
  import { encodeInput, generatePrefetchCode } from '../renderers/utils.js';
9
10
  import { getBuildConfig, getSsrConfig, renderRsc, SERVER_MODULE_MAP } from '../renderers/rsc-renderer.js';
@@ -14,16 +15,16 @@ import { rscAnalyzePlugin } from '../plugins/vite-plugin-rsc-analyze.js';
14
15
  import { nonjsResolvePlugin } from '../plugins/vite-plugin-nonjs-resolve.js';
15
16
  import { rscTransformPlugin } from '../plugins/vite-plugin-rsc-transform.js';
16
17
  import { rscEntriesPlugin } from '../plugins/vite-plugin-rsc-entries.js';
17
- import { rscServePlugin } from '../plugins/vite-plugin-rsc-serve.js';
18
18
  import { rscEnvPlugin } from '../plugins/vite-plugin-rsc-env.js';
19
19
  import { rscPrivatePlugin } from '../plugins/vite-plugin-rsc-private.js';
20
20
  import { rscManagedPlugin } from '../plugins/vite-plugin-rsc-managed.js';
21
- import { emitVercelOutput } from './output-vercel.js';
22
- import { emitNetlifyOutput } from './output-netlify.js';
23
- import { emitCloudflareOutput } from './output-cloudflare.js';
24
- import { emitPartyKitOutput } from './output-partykit.js';
25
- import { emitAwsLambdaOutput } from './output-aws-lambda.js';
26
- import { DIST_ENTRIES_JS, DIST_SERVE_JS, DIST_PUBLIC, DIST_ASSETS, DIST_SSR } from './constants.js';
21
+ import { DIST_ENTRIES_JS, DIST_PUBLIC, DIST_ASSETS, DIST_SSR } from './constants.js';
22
+ import { deployVercelPlugin } from '../plugins/vite-plugin-deploy-vercel.js';
23
+ import { deployNetlifyPlugin } from '../plugins/vite-plugin-deploy-netlify.js';
24
+ import { deployCloudflarePlugin } from '../plugins/vite-plugin-deploy-cloudflare.js';
25
+ import { deployDenoPlugin } from '../plugins/vite-plugin-deploy-deno.js';
26
+ import { deployPartykitPlugin } from '../plugins/vite-plugin-deploy-partykit.js';
27
+ import { deployAwsLambdaPlugin } from '../plugins/vite-plugin-deploy-aws-lambda.js';
27
28
  // TODO this file and functions in it are too long. will fix.
28
29
  // Upstream issue: https://github.com/rollup/rollup/issues/4699
29
30
  const onwarn = (warning, defaultHandler)=>{
@@ -34,6 +35,14 @@ const onwarn = (warning, defaultHandler)=>{
34
35
  }
35
36
  defaultHandler(warning);
36
37
  };
38
+ const deployPlugins = (config)=>[
39
+ deployVercelPlugin(config),
40
+ deployNetlifyPlugin(config),
41
+ deployCloudflarePlugin(config),
42
+ deployDenoPlugin(config),
43
+ deployPartykitPlugin(config),
44
+ deployAwsLambdaPlugin(config)
45
+ ];
37
46
  const analyzeEntries = async (rootDir, config)=>{
38
47
  const wakuClientDist = decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../../client.js'));
39
48
  const clientFileSet = new Set([
@@ -131,7 +140,7 @@ const analyzeEntries = async (rootDir, config)=>{
131
140
  };
132
141
  };
133
142
  // For RSC
134
- const buildServerBundle = async (rootDir, env, config, clientEntryFiles, serverEntryFiles, serverModuleFiles, serve, isNodeCompatible, partial)=>{
143
+ const buildServerBundle = async (rootDir, env, config, clientEntryFiles, serverEntryFiles, serverModuleFiles, isNodeCompatible, partial)=>{
135
144
  const serverBuildOutput = await buildVite({
136
145
  plugins: [
137
146
  nonjsResolvePlugin(),
@@ -174,15 +183,7 @@ const buildServerBundle = async (rootDir, env, config, clientEntryFiles, serverE
174
183
  ]))
175
184
  }
176
185
  }),
177
- ...serve ? [
178
- rscServePlugin({
179
- ...config,
180
- distServeJs: DIST_SERVE_JS,
181
- distPublic: DIST_PUBLIC,
182
- srcServeFile: decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), `../serve-${serve}.js`)),
183
- serve
184
- })
185
- ] : []
186
+ ...deployPlugins(config)
186
187
  ],
187
188
  ssr: isNodeCompatible ? {
188
189
  resolve: {
@@ -429,6 +430,29 @@ const pathSpec2pathname = (pathSpec)=>{
429
430
  }
430
431
  return '/' + pathSpec.map(({ name })=>name).join('/');
431
432
  };
433
+ const willEmitPublicIndexHtml = async (env, config, distEntries, buildConfig)=>{
434
+ const hasConfig = buildConfig.some(({ pathname })=>{
435
+ const pathSpec = typeof pathname === 'string' ? pathname2pathSpec(pathname) : pathname;
436
+ return !!getPathMapping(pathSpec, '/');
437
+ });
438
+ if (!hasConfig) {
439
+ return false;
440
+ }
441
+ try {
442
+ return !!await getSsrConfig({
443
+ env,
444
+ config,
445
+ pathname: '/',
446
+ searchParams: new URLSearchParams()
447
+ }, {
448
+ isDev: false,
449
+ entries: distEntries
450
+ });
451
+ } catch {
452
+ // HACK to pass e2e tests
453
+ return false;
454
+ }
455
+ };
432
456
  const emitHtmlFiles = async (rootDir, env, config, distEntriesFile, distEntries, buildConfig, getClientModules, clientBuildOutput)=>{
433
457
  const nonJsAssets = clientBuildOutput.output.flatMap(({ type, fileName })=>type === 'asset' && !fileName.endsWith('.js') ? [
434
458
  fileName
@@ -439,8 +463,7 @@ const emitHtmlFiles = async (rootDir, env, config, distEntriesFile, distEntries,
439
463
  const publicIndexHtml = await readFile(publicIndexHtmlFile, {
440
464
  encoding: 'utf8'
441
465
  });
442
- if (buildConfig.length) {
443
- // Delete the default index.html file unless buildConfig is empty.
466
+ if (await willEmitPublicIndexHtml(env, config, distEntries, buildConfig)) {
444
467
  await unlink(publicIndexHtmlFile);
445
468
  }
446
469
  const publicIndexHtmlHead = publicIndexHtml.replace(/.*?<head>(.*?)<\/head>.*/s, '$1');
@@ -525,16 +548,70 @@ export const publicIndexHtml = ${JSON.stringify(publicIndexHtml)};
525
548
  `;
526
549
  await appendFile(distEntriesFile, code);
527
550
  };
551
+ // For Deploy
552
+ const buildDeploy = async (rootDir, config)=>{
553
+ const DUMMY = 'dummy-entry';
554
+ await buildVite({
555
+ plugins: [
556
+ {
557
+ // FIXME This is too hacky. There must be a better way.
558
+ name: 'dummy-entry-plugin',
559
+ resolveId (source) {
560
+ if (source === DUMMY) {
561
+ return source;
562
+ }
563
+ },
564
+ load (id) {
565
+ if (id === DUMMY) {
566
+ return '';
567
+ }
568
+ },
569
+ generateBundle (_options, bundle) {
570
+ Object.entries(bundle).forEach(([key, value])=>{
571
+ if (value.name === DUMMY) {
572
+ delete bundle[key];
573
+ }
574
+ });
575
+ }
576
+ },
577
+ ...deployPlugins(config)
578
+ ],
579
+ publicDir: false,
580
+ build: {
581
+ emptyOutDir: false,
582
+ ssr: true,
583
+ rollupOptions: {
584
+ onwarn: (warning, warn)=>{
585
+ if (!warning.message.startsWith('Generated an empty chunk:')) {
586
+ warn(warning);
587
+ }
588
+ },
589
+ input: {
590
+ [DUMMY]: DUMMY
591
+ }
592
+ },
593
+ outDir: joinPath(rootDir, config.distDir)
594
+ }
595
+ });
596
+ };
528
597
  export async function build(options) {
529
598
  const env = options.env || {};
530
599
  const config = await resolveConfig(options.config);
531
600
  const rootDir = (await resolveViteConfig({}, 'build', 'production', 'production')).root;
532
601
  const distEntriesFile = joinPath(rootDir, config.distDir, DIST_ENTRIES_JS);
533
602
  const isNodeCompatible = options.deploy !== 'cloudflare' && options.deploy !== 'partykit' && options.deploy !== 'deno';
603
+ const platformObject = unstable_getPlatformObject();
604
+ platformObject.buildOptions ||= {};
605
+ platformObject.buildOptions.deploy = options.deploy;
606
+ platformObject.buildOptions.unstable_phase = 'analyzeEntries';
534
607
  const { clientEntryFiles, serverEntryFiles, serverModuleFiles } = await analyzeEntries(rootDir, config);
535
- const serverBuildOutput = await buildServerBundle(rootDir, env, config, clientEntryFiles, serverEntryFiles, serverModuleFiles, (options.deploy === 'vercel-serverless' ? 'vercel' : false) || (options.deploy === 'netlify-functions' ? 'netlify' : false) || (options.deploy === 'cloudflare' ? 'cloudflare' : false) || (options.deploy === 'partykit' ? 'partykit' : false) || (options.deploy === 'deno' ? 'deno' : false) || (options.deploy === 'aws-lambda' ? 'aws-lambda' : false), isNodeCompatible, !!options.partial);
608
+ platformObject.buildOptions.unstable_phase = 'buildServerBundle';
609
+ const serverBuildOutput = await buildServerBundle(rootDir, env, config, clientEntryFiles, serverEntryFiles, serverModuleFiles, isNodeCompatible, !!options.partial);
610
+ platformObject.buildOptions.unstable_phase = 'buildSsrBundle';
536
611
  await buildSsrBundle(rootDir, env, config, clientEntryFiles, serverEntryFiles, serverBuildOutput, isNodeCompatible, !!options.partial);
612
+ platformObject.buildOptions.unstable_phase = 'buildClientBundle';
537
613
  const clientBuildOutput = await buildClientBundle(rootDir, env, config, clientEntryFiles, serverEntryFiles, serverBuildOutput, !!options.partial);
614
+ delete platformObject.buildOptions.unstable_phase;
538
615
  const distEntries = await import(filePathToFileURL(distEntriesFile));
539
616
  // TODO: Add progress indication for static builds.
540
617
  const buildConfig = await getBuildConfig({
@@ -543,18 +620,10 @@ export async function build(options) {
543
620
  }, {
544
621
  entries: distEntries
545
622
  });
546
- await appendFile(distEntriesFile, `export const buildConfig = ${JSON.stringify(buildConfig)};`);
547
623
  const { getClientModules } = await emitRscFiles(rootDir, env, config, distEntries, buildConfig);
548
624
  await emitHtmlFiles(rootDir, env, config, distEntriesFile, distEntries, buildConfig, getClientModules, clientBuildOutput);
549
- if (options.deploy?.startsWith('vercel-')) {
550
- await emitVercelOutput(rootDir, config, DIST_SERVE_JS, options.deploy.slice('vercel-'.length));
551
- } else if (options.deploy?.startsWith('netlify-')) {
552
- await emitNetlifyOutput(rootDir, config, DIST_SERVE_JS, options.deploy.slice('netlify-'.length));
553
- } else if (options.deploy === 'cloudflare') {
554
- await emitCloudflareOutput(rootDir, config, DIST_SERVE_JS);
555
- } else if (options.deploy === 'partykit') {
556
- await emitPartyKitOutput(rootDir, config, DIST_SERVE_JS);
557
- } else if (options.deploy === 'aws-lambda') {
558
- await emitAwsLambdaOutput(config);
559
- }
625
+ platformObject.buildOptions.unstable_phase = 'buildDeploy';
626
+ await buildDeploy(rootDir, config);
627
+ delete platformObject.buildOptions.unstable_phase;
628
+ await appendFile(distEntriesFile, `export const buildData = ${JSON.stringify(platformObject.buildData)};`);
560
629
  }
@@ -127,15 +127,17 @@ const createMainViteServer = (env, configPromise)=>{
127
127
  return vite;
128
128
  });
129
129
  const loadServerModuleMain = async (idOrFileURL)=>{
130
+ const vite = await vitePromise;
130
131
  if (idOrFileURL === 'waku' || idOrFileURL.startsWith('waku/')) {
131
132
  // HACK `external: ['waku']` doesn't do the same
132
133
  return import(/* @vite-ignore */ idOrFileURL);
133
134
  }
134
135
  if (idOrFileURL.startsWith('file://') && idOrFileURL.includes('/node_modules/')) {
135
136
  // HACK node_modules should be externalized
136
- return import(/* @vite-ignore */ fileURLToFilePath(idOrFileURL));
137
+ const file = fileURLToFilePath(idOrFileURL);
138
+ const fileWithAbsolutePath = file.startsWith('/') ? file : joinPath(vite.config.root, file);
139
+ return import(/* @vite-ignore */ fileWithAbsolutePath);
137
140
  }
138
- const vite = await vitePromise;
139
141
  return vite.ssrLoadModule(idOrFileURL.startsWith('file://') ? fileURLToFilePath(idOrFileURL) : idOrFileURL);
140
142
  };
141
143
  const transformIndexHtml = async (pathname)=>{
@@ -15,7 +15,7 @@ export const rsc = (options)=>{
15
15
  if (ctx.req.url.pathname.startsWith(basePrefix)) {
16
16
  const { headers } = ctx.req;
17
17
  try {
18
- const input = decodeInput(ctx.req.url.pathname.slice(basePrefix.length));
18
+ const input = decodeInput(decodeURI(ctx.req.url.pathname.slice(basePrefix.length)));
19
19
  const args = {
20
20
  env,
21
21
  config,
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function deployAwsLambdaPlugin(opts: {
3
+ srcDir: string;
4
+ distDir: string;
5
+ }): Plugin;
@@ -0,0 +1,52 @@
1
+ import path from 'node:path';
2
+ import { existsSync, writeFileSync } from 'node:fs';
3
+ import { normalizePath } from 'vite';
4
+ // HACK: Depending on a different plugin isn't ideal.
5
+ // Maybe we could put in vite config object?
6
+ import { SRC_ENTRIES } from './vite-plugin-rsc-managed.js';
7
+ import { unstable_getPlatformObject } from '../../server.js';
8
+ import { EXTENSIONS } from '../config.js';
9
+ import { decodeFilePathFromAbsolute, extname, fileURLToFilePath, joinPath } from '../utils/path.js';
10
+ import { DIST_SERVE_JS, DIST_PUBLIC } from '../builder/constants.js';
11
+ const resolveFileName = (fname)=>{
12
+ for (const ext of EXTENSIONS){
13
+ const resolvedName = fname.slice(0, -extname(fname).length) + ext;
14
+ if (existsSync(resolvedName)) {
15
+ return resolvedName;
16
+ }
17
+ }
18
+ return fname; // returning the default one
19
+ };
20
+ const srcServeFile = decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../builder/serve-aws-lambda.js'));
21
+ export function deployAwsLambdaPlugin(opts) {
22
+ const platformObject = unstable_getPlatformObject();
23
+ return {
24
+ name: 'deploy-aws-lambda-plugin',
25
+ config (viteConfig) {
26
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
27
+ if (unstable_phase !== 'buildServerBundle' || deploy !== 'aws-lambda') {
28
+ return;
29
+ }
30
+ // FIXME This seems too hacky (The use of viteConfig.root, '.', path.resolve and resolveFileName)
31
+ const entriesFile = normalizePath(resolveFileName(path.resolve(viteConfig.root || '.', opts.srcDir, SRC_ENTRIES + '.jsx')));
32
+ const { input } = viteConfig.build?.rollupOptions ?? {};
33
+ if (input && !(typeof input === 'string') && !(input instanceof Array)) {
34
+ input[DIST_SERVE_JS.replace(/\.js$/, '')] = srcServeFile;
35
+ }
36
+ viteConfig.define = {
37
+ ...viteConfig.define,
38
+ 'import.meta.env.WAKU_ENTRIES_FILE': JSON.stringify(entriesFile),
39
+ 'import.meta.env.WAKU_CONFIG_PUBLIC_DIR': JSON.stringify(DIST_PUBLIC)
40
+ };
41
+ },
42
+ closeBundle () {
43
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
44
+ if (unstable_phase !== 'buildDeploy' || deploy !== 'aws-lambda') {
45
+ return;
46
+ }
47
+ writeFileSync(path.join(opts.distDir, 'package.json'), JSON.stringify({
48
+ type: 'module'
49
+ }, null, 2));
50
+ }
51
+ };
52
+ }
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function deployCloudflarePlugin(opts: {
3
+ srcDir: string;
4
+ distDir: string;
5
+ rscPath: string;
6
+ privateDir: string;
7
+ }): Plugin;
@@ -0,0 +1,143 @@
1
+ import path from 'node:path';
2
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { normalizePath } from 'vite';
4
+ // HACK: Depending on a different plugin isn't ideal.
5
+ // Maybe we could put in vite config object?
6
+ import { SRC_ENTRIES } from './vite-plugin-rsc-managed.js';
7
+ import { unstable_getPlatformObject } from '../../server.js';
8
+ import { EXTENSIONS } from '../config.js';
9
+ import { decodeFilePathFromAbsolute, extname, fileURLToFilePath, joinPath } from '../utils/path.js';
10
+ import { DIST_SERVE_JS, DIST_PUBLIC } from '../builder/constants.js';
11
+ const resolveFileName = (fname)=>{
12
+ for (const ext of EXTENSIONS){
13
+ const resolvedName = fname.slice(0, -extname(fname).length) + ext;
14
+ if (existsSync(resolvedName)) {
15
+ return resolvedName;
16
+ }
17
+ }
18
+ return fname; // returning the default one
19
+ };
20
+ const srcServeFile = decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../builder/serve-cloudflare.js'));
21
+ const getFiles = (dir, files = [])=>{
22
+ const entries = readdirSync(dir, {
23
+ withFileTypes: true
24
+ });
25
+ for (const entry of entries){
26
+ const fullPath = path.join(dir, entry.name);
27
+ if (entry.isDirectory()) {
28
+ getFiles(fullPath, files);
29
+ } else {
30
+ files.push(fullPath);
31
+ }
32
+ }
33
+ return files;
34
+ };
35
+ const WORKER_JS_NAME = '_worker.js';
36
+ const ROUTES_JSON_NAME = '_routes.json';
37
+ const HEADERS_NAME = '_headers';
38
+ export function deployCloudflarePlugin(opts) {
39
+ const platformObject = unstable_getPlatformObject();
40
+ let rootDir;
41
+ return {
42
+ name: 'deploy-cloudflare-plugin',
43
+ config (viteConfig) {
44
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
45
+ if (unstable_phase !== 'buildServerBundle' || deploy !== 'cloudflare') {
46
+ return;
47
+ }
48
+ // FIXME This seems too hacky (The use of viteConfig.root, '.', path.resolve and resolveFileName)
49
+ const entriesFile = normalizePath(resolveFileName(path.resolve(viteConfig.root || '.', opts.srcDir, SRC_ENTRIES + '.jsx')));
50
+ const { input } = viteConfig.build?.rollupOptions ?? {};
51
+ if (input && !(typeof input === 'string') && !(input instanceof Array)) {
52
+ input[DIST_SERVE_JS.replace(/\.js$/, '')] = srcServeFile;
53
+ }
54
+ viteConfig.define = {
55
+ ...viteConfig.define,
56
+ 'import.meta.env.WAKU_ENTRIES_FILE': JSON.stringify(entriesFile)
57
+ };
58
+ },
59
+ configResolved (config) {
60
+ rootDir = config.root;
61
+ },
62
+ closeBundle () {
63
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
64
+ if (unstable_phase !== 'buildDeploy' || deploy !== 'cloudflare') {
65
+ return;
66
+ }
67
+ const outDir = path.join(rootDir, opts.distDir);
68
+ // Advanced-mode Cloudflare Pages imports _worker.js
69
+ // and can be configured with _routes.json to serve other static root files
70
+ mkdirSync(path.join(outDir, WORKER_JS_NAME));
71
+ const outPaths = readdirSync(outDir);
72
+ for (const p of outPaths){
73
+ if (p === WORKER_JS_NAME) {
74
+ continue;
75
+ }
76
+ renameSync(path.join(outDir, p), path.join(outDir, WORKER_JS_NAME, p));
77
+ }
78
+ const workerEntrypoint = path.join(outDir, WORKER_JS_NAME, 'index.js');
79
+ if (!existsSync(workerEntrypoint)) {
80
+ writeFileSync(workerEntrypoint, `
81
+ import server from './${DIST_SERVE_JS}'
82
+
83
+ export default {
84
+ ...server
85
+ }
86
+ `);
87
+ }
88
+ // Create _routes.json if one doesn't already exist in the public dir
89
+ // https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes
90
+ const routesFile = path.join(outDir, ROUTES_JSON_NAME);
91
+ const publicDir = path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC);
92
+ if (!existsSync(path.join(publicDir, ROUTES_JSON_NAME))) {
93
+ // exclude strategy
94
+ const staticPaths = [
95
+ '/assets/*'
96
+ ];
97
+ const paths = getFiles(publicDir);
98
+ for (const p of paths){
99
+ const basePath = path.dirname(p.replace(publicDir, '')) || '/';
100
+ const name = path.basename(p);
101
+ const entry = name === 'index.html' ? basePath + (basePath !== '/' ? '/' : '') : path.join(basePath, name.replace(/\.html$/, ''));
102
+ if (entry.startsWith('/assets/') || entry.startsWith('/' + WORKER_JS_NAME + '/') || entry === '/' + WORKER_JS_NAME || entry === '/' + ROUTES_JSON_NAME || entry === '/' + HEADERS_NAME) {
103
+ continue;
104
+ }
105
+ if (!staticPaths.includes(entry)) {
106
+ staticPaths.push(entry);
107
+ }
108
+ }
109
+ const MAX_CLOUDFLARE_RULES = 100;
110
+ if (staticPaths.length + 1 > MAX_CLOUDFLARE_RULES) {
111
+ throw new Error(`The number of static paths exceeds the limit of ${MAX_CLOUDFLARE_RULES}. ` + `You need to create a custom ${ROUTES_JSON_NAME} file in the public folder. ` + `See https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes`);
112
+ }
113
+ const staticRoutes = {
114
+ version: 1,
115
+ include: [
116
+ '/*'
117
+ ],
118
+ exclude: staticPaths
119
+ };
120
+ writeFileSync(routesFile, JSON.stringify(staticRoutes));
121
+ }
122
+ // Move the public files to the root of the dist folder
123
+ const publicPaths = readdirSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC));
124
+ for (const p of publicPaths){
125
+ renameSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC, p), path.join(outDir, p));
126
+ }
127
+ rmSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC), {
128
+ recursive: true,
129
+ force: true
130
+ });
131
+ const wranglerTomlFile = path.join(rootDir, 'wrangler.toml');
132
+ if (!existsSync(wranglerTomlFile)) {
133
+ writeFileSync(wranglerTomlFile, `
134
+ # See https://developers.cloudflare.com/pages/functions/wrangler-configuration/
135
+ name = "waku-project"
136
+ compatibility_date = "2024-04-03"
137
+ compatibility_flags = [ "nodejs_als" ]
138
+ pages_build_output_dir = "./dist"
139
+ `);
140
+ }
141
+ }
142
+ };
143
+ }
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function deployDenoPlugin(opts: {
3
+ srcDir: string;
4
+ distDir: string;
5
+ }): Plugin;
@@ -1,11 +1,13 @@
1
- import { existsSync } from 'node:fs';
2
1
  import path from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
3
  import { normalizePath } from 'vite';
4
4
  // HACK: Depending on a different plugin isn't ideal.
5
5
  // Maybe we could put in vite config object?
6
6
  import { SRC_ENTRIES } from './vite-plugin-rsc-managed.js';
7
+ import { unstable_getPlatformObject } from '../../server.js';
7
8
  import { EXTENSIONS } from '../config.js';
8
- import { extname } from '../utils/path.js';
9
+ import { decodeFilePathFromAbsolute, extname, fileURLToFilePath, joinPath } from '../utils/path.js';
10
+ import { DIST_SERVE_JS, DIST_PUBLIC } from '../builder/constants.js';
9
11
  const resolveFileName = (fname)=>{
10
12
  for (const ext of EXTENSIONS){
11
13
  const resolvedName = fname.slice(0, -extname(fname).length) + ext;
@@ -15,32 +17,28 @@ const resolveFileName = (fname)=>{
15
17
  }
16
18
  return fname; // returning the default one
17
19
  };
18
- export function rscServePlugin(opts) {
20
+ const srcServeFile = decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../builder/serve-deno.js'));
21
+ export function deployDenoPlugin(opts) {
22
+ const platformObject = unstable_getPlatformObject();
19
23
  return {
20
- name: 'rsc-serve-plugin',
24
+ name: 'deploy-deno-plugin',
21
25
  config (viteConfig) {
26
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
27
+ if (unstable_phase !== 'buildServerBundle' || deploy !== 'deno') {
28
+ return;
29
+ }
22
30
  // FIXME This seems too hacky (The use of viteConfig.root, '.', path.resolve and resolveFileName)
23
31
  const entriesFile = normalizePath(resolveFileName(path.resolve(viteConfig.root || '.', opts.srcDir, SRC_ENTRIES + '.jsx')));
24
32
  const { input } = viteConfig.build?.rollupOptions ?? {};
25
33
  if (input && !(typeof input === 'string') && !(input instanceof Array)) {
26
- input[opts.distServeJs.replace(/\.js$/, '')] = opts.srcServeFile;
34
+ input[DIST_SERVE_JS.replace(/\.js$/, '')] = srcServeFile;
27
35
  }
28
36
  viteConfig.define = {
29
37
  ...viteConfig.define,
30
38
  'import.meta.env.WAKU_ENTRIES_FILE': JSON.stringify(entriesFile),
31
39
  'import.meta.env.WAKU_CONFIG_DIST_DIR': JSON.stringify(opts.distDir),
32
- 'import.meta.env.WAKU_CONFIG_PUBLIC_DIR': JSON.stringify(opts.distPublic)
40
+ 'import.meta.env.WAKU_CONFIG_PUBLIC_DIR': JSON.stringify(DIST_PUBLIC)
33
41
  };
34
- if (opts.serve === 'partykit') {
35
- viteConfig.build ||= {};
36
- viteConfig.build.rollupOptions ||= {};
37
- viteConfig.build.rollupOptions.external ||= [];
38
- if (Array.isArray(viteConfig.build.rollupOptions.external)) {
39
- viteConfig.build.rollupOptions.external.push('hono');
40
- } else {
41
- throw new Error('Unsupported: build.rollupOptions.external is not an array');
42
- }
43
- }
44
42
  }
45
43
  };
46
44
  }
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function deployNetlifyPlugin(opts: {
3
+ srcDir: string;
4
+ distDir: string;
5
+ privateDir: string;
6
+ }): Plugin;
@@ -0,0 +1,78 @@
1
+ import path from 'node:path';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { normalizePath } from 'vite';
4
+ // HACK: Depending on a different plugin isn't ideal.
5
+ // Maybe we could put in vite config object?
6
+ import { SRC_ENTRIES } from './vite-plugin-rsc-managed.js';
7
+ import { unstable_getPlatformObject } from '../../server.js';
8
+ import { EXTENSIONS } from '../config.js';
9
+ import { decodeFilePathFromAbsolute, extname, fileURLToFilePath, joinPath } from '../utils/path.js';
10
+ import { DIST_SERVE_JS, DIST_PUBLIC } from '../builder/constants.js';
11
+ const resolveFileName = (fname)=>{
12
+ for (const ext of EXTENSIONS){
13
+ const resolvedName = fname.slice(0, -extname(fname).length) + ext;
14
+ if (existsSync(resolvedName)) {
15
+ return resolvedName;
16
+ }
17
+ }
18
+ return fname; // returning the default one
19
+ };
20
+ const srcServeFile = decodeFilePathFromAbsolute(joinPath(fileURLToFilePath(import.meta.url), '../../builder/serve-netlify.js'));
21
+ export function deployNetlifyPlugin(opts) {
22
+ const platformObject = unstable_getPlatformObject();
23
+ let rootDir;
24
+ return {
25
+ name: 'deploy-netlify-plugin',
26
+ config (viteConfig) {
27
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
28
+ if (unstable_phase !== 'buildServerBundle' || deploy !== 'netlify-functions' && deploy !== 'netlify-static') {
29
+ return;
30
+ }
31
+ // FIXME This seems too hacky (The use of viteConfig.root, '.', path.resolve and resolveFileName)
32
+ const entriesFile = normalizePath(resolveFileName(path.resolve(viteConfig.root || '.', opts.srcDir, SRC_ENTRIES + '.jsx')));
33
+ const { input } = viteConfig.build?.rollupOptions ?? {};
34
+ if (input && !(typeof input === 'string') && !(input instanceof Array)) {
35
+ input[DIST_SERVE_JS.replace(/\.js$/, '')] = srcServeFile;
36
+ }
37
+ viteConfig.define = {
38
+ ...viteConfig.define,
39
+ 'import.meta.env.WAKU_ENTRIES_FILE': JSON.stringify(entriesFile)
40
+ };
41
+ },
42
+ configResolved (config) {
43
+ rootDir = config.root;
44
+ },
45
+ closeBundle () {
46
+ const { deploy, unstable_phase } = platformObject.buildOptions || {};
47
+ if (unstable_phase !== 'buildDeploy' || deploy !== 'netlify-functions' && deploy !== 'netlify-static') {
48
+ return;
49
+ }
50
+ if (deploy === 'netlify-functions') {
51
+ const functionsDir = path.join(rootDir, 'netlify/functions');
52
+ mkdirSync(functionsDir, {
53
+ recursive: true
54
+ });
55
+ const notFoundFile = path.join(rootDir, opts.distDir, DIST_PUBLIC, '404.html');
56
+ const notFoundHtml = existsSync(notFoundFile) ? readFileSync(notFoundFile, 'utf8') : null;
57
+ writeFileSync(path.join(functionsDir, 'serve.js'), `
58
+ globalThis.__WAKU_NOT_FOUND_HTML__ = ${JSON.stringify(notFoundHtml)};
59
+ export { default } from '../../${opts.distDir}/${DIST_SERVE_JS}';
60
+ export const config = {
61
+ preferStatic: true,
62
+ path: ['/', '/*'],
63
+ };
64
+ `);
65
+ }
66
+ const netlifyTomlFile = path.join(rootDir, 'netlify.toml');
67
+ if (!existsSync(netlifyTomlFile)) {
68
+ writeFileSync(netlifyTomlFile, `
69
+ [build]
70
+ command = "npm run build -- --with-netlify"
71
+ publish = "${opts.distDir}/${DIST_PUBLIC}"
72
+ [functions]
73
+ included_files = ["${opts.privateDir}/**"]
74
+ `);
75
+ }
76
+ }
77
+ };
78
+ }
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function deployPartykitPlugin(opts: {
3
+ srcDir: string;
4
+ distDir: string;
5
+ }): Plugin;