diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index ccd2a26ca2fc..96ad64398639 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -2,13 +2,13 @@ title: Cloudflare --- -To deploy to [Cloudflare Workers](https://workers.cloudflare.com/) or [Cloudflare Pages](https://pages.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). +To deploy to [Cloudflare Workers](https://workers.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). -This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `event.platform` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare Workers, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `event.platform` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. ## Comparisons -- `adapter-cloudflare` – supports all SvelteKit features; builds for Cloudflare Workers Static Assets and Cloudflare Pages +- `adapter-cloudflare` – supports all SvelteKit features; builds for Cloudflare Workers Static Assets - `adapter-cloudflare-workers` – deprecated. Supports all SvelteKit features; builds for Cloudflare Workers Sites - `adapter-static` – only produces client-side static assets; compatible with Cloudflare Workers Static Assets and Cloudflare Pages @@ -23,112 +23,69 @@ import adapter from '@sveltejs/adapter-cloudflare'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { - adapter: adapter({ - // See below for an explanation of these options - config: undefined, - platformProxy: { - configPath: undefined, - environment: undefined, - persist: undefined - }, - fallback: 'plaintext', - routes: { - include: ['/*'], - exclude: [''] - } - }) + adapter: adapter() } }; export default config; ``` -## Options - -### config - -Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). If you would like to use a Wrangler configuration filename other than `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml` you can specify it using this option. - -### platformProxy - -Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. - -### fallback - -Whether to render a plaintext 404.html page or a rendered SPA fallback page for non-matching asset requests. - -For Cloudflare Workers, the default behaviour is to return a null-body 404-status response for non-matching assets requests. However, if the [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) Wrangler configuration setting is set to `"404-page"`, this page will be served if a request fails to match an asset. If `assets.not_found_handling` is set to `"single-page-application"`, the adapter will render a SPA fallback `index.html` page regardless of the `fallback` option specified. +And also to your `vite.config.js`: -For Cloudflare Pages, this page will only be served when a request that matches an entry in `routes.exclude` fails to match an asset. - -Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to use `spa` instead to avoid showing an unstyled 404 page to users. - -See Cloudflare Pages' [Not Found behaviour](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. - -### routes - -Only for Cloudflare Pages. Allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. +```js +/// file: vite.config.js +import adapter from '@sveltejs/adapter-cloudflare'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + sveltekit({ + adapter: adapter({ + // See below for an explanations of these options + vitePluginOptions: undefined + }) + }) + ] +}); +``` -- `include` defines routes that will invoke a function, and defaults to `['/*']` -- `exclude` defines routes that will _not_ invoke a function — this is a faster and cheaper way to serve your app's static assets. This array can include the following special values: - - `` contains your app's build artifacts (the files generated by Vite) - - `` contains the contents of your `static` directory - - `` contains a list of pathnames from your [`_redirects` file](https://developers.cloudflare.com/pages/configuration/redirects/) at the root - - `` contains a list of prerendered pages - - `` (the default) contains all of the above +## Options -You can have up to 100 `include` and `exclude` rules combined. Generally you can omit the `routes` options, but if (for example) your `` paths exceed that limit, you may find it helpful to manually create an `exclude` list that includes `'/articles/*'` instead of the auto-generated `['/articles/foo', '/articles/bar', '/articles/baz', ...]`. +### vitePluginOptions -## Cloudflare Workers +// TODO: -### Basic configuration +## Customising your worker -When building for Cloudflare Workers, this adapter expects to find a [Wrangler configuration file](https://developers.cloudflare.com/workers/configuration/sites/configuration/) in the project root. It should look something like this: +You can customise your worker by creating a new file and specifying the path to it in your [Wrangler configuration file](https://developers.cloudflare.com/workers/configuration/sites/configuration/). ```jsonc /// file: wrangler.jsonc { - "name": "", - "main": ".svelte-kit/cloudflare/_worker.js", - "compatibility_flags": ["nodejs_als"], - "compatibility_date": "", - "assets": { - "binding": "ASSETS", - "directory": ".svelte-kit/cloudflare", - } + "main": "src/worker.js", } ``` -### Deployment - -You can use the Wrangler CLI to deploy your application by running `npx wrangler deploy` or use the [Cloudflare Git integration](https://developers.cloudflare.com/workers/ci-cd/builds/) to enable automatic builds and deployments on push. - -## Cloudflare Pages - -### Deployment - -Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started/) for Cloudflare Pages to begin. - -If you're using the [Git integration](https://developers.cloudflare.com/pages/get-started/git-integration/), your build settings should look like this: - -- Framework preset – SvelteKit -- Build command – `npm run build` or `vite build` -- Build output directory – `.svelte-kit/cloudflare` - +Inside your worker file, you can reproduce the SvelteKit request handling behaviour by importing the `fetch` method from `@sveltejs/adapter-cloudflare/worker`. -Once configured, go to the **Runtime** section of your project settings, and add the `nodejs_als` compatibility flag to enable the [Node.js AsyncLocalStorage](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-asynclocalstorage). Alternatively, do this in your wrangler config using the `compatibility_flags` array. - -### Further reading +```js +/// file: src/worker.js +import { fetch } from '@sveltejs/adapter-cloudflare/worker'; -You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site on Cloudflare Pages](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). +export default { + fetch + // Add other types of handlers here +} +``` -### Notes +## Deployment -Functions contained in the [`/functions` directory](https://developers.cloudflare.com/pages/functions/routing/) at the project's root will _not_ be included in the deployment. Instead, functions should be implemented as [server endpoints](routing#server) in your SvelteKit app, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/functions/advanced-mode/). +You can use the Wrangler CLI to deploy your application by running `npx wrangler deploy` or use the [Cloudflare Git integration](https://developers.cloudflare.com/workers/ci-cd/builds/) to enable automatic builds and deployments on push. ## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties) meaning that you can access it in hooks and endpoints: ```js // @filename: ambient.d.ts @@ -155,19 +112,16 @@ export async function POST({ request, platform }) { > [!NOTE] SvelteKit's built-in [`$env` module]($env-static-private) should be preferred for environment variables. -To make these types available to your app, install [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) and reference them in your `src/app.d.ts`: +After configuring the bindings in your Wrangler configuration file, you can make the types available by running [`npx wrangler types`](https://developers.cloudflare.com/workers/languages/typescript/) and referencing the `Env` type in your `src/app.d.ts`: ```ts /// file: src/app.d.ts -+++import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types';+++ ++++import { Env } from '../worker-configuration.d.ts';+++ declare global { namespace App { interface Platform { -+++ env: { - YOUR_KV_NAMESPACE: KVNamespace; - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; - };+++ ++++ env: Env;+++ } } } @@ -177,9 +131,7 @@ export {}; ### Testing locally -Cloudflare specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. - -For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) version 4. Once you have built your site, run `wrangler dev .svelte-kit/cloudflare/_worker.js` if you're testing for Cloudflare Workers or `wrangler pages dev .svelte-kit/cloudflare` for Cloudflare Pages. +Cloudflare specific values in the `platform` property are emulated during dev and preview modes through [Cloudflare's Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/). [Bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env`. ## Headers and redirects @@ -212,6 +164,10 @@ Instead, use the [`read`]($app-server#read) function from `$app/server` to acces Alternatively, you can [prerender](page-options#prerender) the routes in question. +## Migrating from Pages + +TODO: + ## Migrating from Workers Sites Cloudflare no longer recommends using [Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/configuration/) and instead recommends using [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/). To migrate, replace `@sveltejs/adapter-cloudflare-workers` with `@sveltejs/adapter-cloudflare` and remove all `site` configuration settings from your Wrangler configuration file, then add the `assets.directory` and `assets.binding` configuration settings: diff --git a/packages/adapter-cloudflare/ambient.d.ts b/packages/adapter-cloudflare/ambient.d.ts index c485bb610945..a02dc1bdb140 100644 --- a/packages/adapter-cloudflare/ambient.d.ts +++ b/packages/adapter-cloudflare/ambient.d.ts @@ -10,7 +10,7 @@ declare global { env: unknown; ctx: ExecutionContext; caches: CacheStorage; - cf?: IncomingRequestCfProperties; + cf: IncomingRequestCfProperties; } } } diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index b9062094bd08..44c6ec9a730c 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,79 +1,39 @@ +import { PluginConfig } from '@cloudflare/vite-plugin'; import { Adapter } from '@sveltejs/kit'; import './ambient.js'; -import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; export interface AdapterOptions { /** - * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). + * Options to pass to the Cloudflare Vite plugin. + * @see https://developers.cloudflare.com/workers/vite-plugin/reference/api/#interface-pluginconfig */ - config?: string; + vitePluginOptions?: PluginConfig; /** * Whether to render a plaintext 404.html page or a rendered SPA fallback page * for non-matching asset requests. * - * For Cloudflare Workers, the default behaviour is to return a null-body + * The default behaviour is to return a null-body * 404-status response for non-matching assets requests. However, if the * [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) * Wrangler configuration setting is set to `"404-page"`, this page will be * served if a request fails to match an asset. If `assets.not_found_handling` * is set to `"single-page-application"`, the adapter will render a SPA fallback * `index.html` page regardless of the `fallback` option specified. - * - * For Cloudflare Pages, this page will only be served when a request that - * matches an entry in `routes.exclude` fails to match an asset. - * - * Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually - * exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to - * use `spa` instead to avoid showing an unstyled 404 page to users. - * - * See [Cloudflare Pages' Not Found behavior](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. - * * @default 'plaintext' + * @deprecated removed in 8.0.0. Configure `assets.not_found_handling` in your Wrangler configuration file instead */ fallback?: 'plaintext' | 'spa'; - - /** - * Only for Cloudflare Pages. Customize the automatically-generated [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file. - */ - routes?: { - /** - * Routes that will be invoked by functions. Accepts wildcards. - * @default ["/*"] - */ - include?: string[]; - - /** - * Routes that will not be invoked by functions. Accepts wildcards. - * `exclude` takes priority over `include`. - * - * To have the adapter automatically exclude certain things, you can use these placeholders: - * - * - `` to exclude build artifacts (files generated by Vite) - * - `` for the contents of your `static` directory - * - `` for prerendered routes - * - `` to exclude all of the above - * - * @default [""] - */ - exclude?: string[]; - }; - /** * Config object passed to [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) * during development and preview. + * @deprecated removed in 8.0.0. Use `vitePluginOptions` instead */ - platformProxy?: GetPlatformProxyOptions; -} - -/** - * The JSON format of the {@link https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file | `_routes.json`} - * file that controls when the Cloudflare Pages Function is invoked. - */ -export interface RoutesJSONSpec { - version: 1; - description: string; - include: string[]; - exclude: string[]; + platformProxy?: Record; + /** + * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). + * @deprecated removed in 8.0.0. Use `vitePluginOptions.configPath` instead + */ + config?: string; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 01186988e4e4..88bc18a43470 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,28 +1,34 @@ -import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { copyFileSync, existsSync, writeFileSync } from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; -import { fileURLToPath } from 'node:url'; -import { unstable_readConfig } from 'wrangler'; -import { - is_building_for_cloudflare_pages, - validate_worker_settings, - get_routes_json, - parse_redirects -} from './utils.js'; +import { cloudflare } from '@cloudflare/vite-plugin'; +import { DEV } from 'esm-env'; const name = '@sveltejs/adapter-cloudflare'; +const default_worker = path.join(import.meta.dirname, 'files/worker.js'); + +/** + * Resolved after the Cloudflare Vite plugin `config` hook runs + * @type {import('@cloudflare/vite-plugin').WorkerConfig} + */ +let wrangler_config; + +/** @type {boolean} */ +let building; + /** @type {import('./index.js').default} */ export default function (options = {}) { return { name, async adapt(builder) { + // TODO: remove in a future major when users have had time to migrate to Cloudflare Workers + let routes_json_path = '_routes.json'; if ( - existsSync('_routes.json') || - existsSync(`${builder.config.kit.files.assets}/_routes.json`) + existsSync(routes_json_path) || + existsSync((routes_json_path = `${builder.config.kit.files.assets}/_routes.json`)) ) { throw new Error( - "Cloudflare Pages' _routes.json should be configured in svelte.config.js. See https://svelte.dev/docs/kit/adapter-cloudflare#Options-routes" + `The ${routes_json_path} file is only used when deploying to Cloudflare Pages. However, this adapter no longer supports it. If you're migrating to Cloudflare Workers, you should remove this file` ); } @@ -38,101 +44,40 @@ export default function (options = {}) { ); } - const { wrangler_config, building_for_cloudflare_pages } = validate_wrangler_config( - options.config - ); - - let dest = builder.getBuildDirectory('cloudflare'); - let worker_dest = `${dest}/_worker.js`; - let assets_binding = 'ASSETS'; + // client assets and prerendered pages + const client_dest = builder.getClientDirectory(); - if (building_for_cloudflare_pages) { - if (wrangler_config.pages_build_output_dir) { - dest = wrangler_config.pages_build_output_dir; - worker_dest = `${dest}/_worker.js`; - } - } else { - if (wrangler_config.main) { - worker_dest = wrangler_config.main; - } - if (wrangler_config.assets?.directory) { - // wrangler doesn't resolve `assets.directory` to an absolute path unlike - // `main` and `pages_build_output_dir` so we need to do it ourselves here - const parent_dir = wrangler_config.configPath - ? path.dirname(path.resolve(wrangler_config.configPath)) - : process.cwd(); - dest = path.resolve(parent_dir, wrangler_config.assets.directory); - } - if (wrangler_config.assets?.binding) { - assets_binding = wrangler_config.assets.binding; - } + // generate plaintext 404.html first which can then be overridden by + // prerendering, if the user defined such a page. + if (wrangler_config.assets?.not_found_handling === '404-page') { + await builder.generateFallback(path.join(client_dest, '404.html')); } - const files = fileURLToPath(new URL('./files', import.meta.url).href); - const tmp = builder.getBuildDirectory('cloudflare-tmp'); + builder.writePrerendered(client_dest); - builder.rimraf(dest); - builder.rimraf(worker_dest); - - builder.mkdirp(dest); - builder.mkdirp(tmp); - - // client assets and prerendered pages - const assets_dest = `${dest}${builder.config.kit.paths.base}`; - builder.mkdirp(assets_dest); - if ( - building_for_cloudflare_pages || - wrangler_config.assets?.not_found_handling === '404-page' - ) { - // generate plaintext 404.html first which can then be overridden by prerendering, if the user defined such a page. - // This file is served when a request fails to match an asset. - // If we're building for Cloudflare Pages, it's only served when a request matches an entry in `routes.exclude` - const fallback = path.join(assets_dest, '404.html'); - if (options.fallback === 'spa') { - await builder.generateFallback(fallback); - } else { - writeFileSync(fallback, 'Not Found'); - } - } - const client_assets = builder.writeClient(assets_dest); - builder.writePrerendered(assets_dest); - if ( - !building_for_cloudflare_pages && - wrangler_config.assets?.not_found_handling === 'single-page-application' - ) { - await builder.generateFallback(path.join(assets_dest, 'index.html')); + if (wrangler_config.assets?.not_found_handling === 'single-page-application') { + await builder.generateFallback(path.join(client_dest, 'index.html')); } // worker - const worker_dest_dir = path.dirname(worker_dest); + const server_dest = builder.getServerDirectory(); writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath: path.posix.relative(tmp, builder.getServerDirectory()) })};\n\n` + + `${server_dest}/manifest.js`, + `export const manifest = ${builder.generateManifest({ relativePath: '.' })};\n\n` + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n\n` + `export const base_path = ${JSON.stringify(builder.config.kit.paths.base)};\n` ); - builder.copy(`${files}/worker.js`, worker_dest, { - replace: { - // the paths returned by the Wrangler config might be Windows paths, - // so we need to convert them to POSIX paths or else the backslashes - // will be interpreted as escape characters and create an incorrect import path. - // We also need to ensure the relative imports start with ./ since Wrangler - // errors if a relative import looks like a package import - SERVER: `./${posixify(path.relative(worker_dest_dir, builder.getServerDirectory()))}/index.js`, - MANIFEST: `./${posixify(path.relative(worker_dest_dir, tmp))}/manifest.js`, - ASSETS: assets_binding - } - }); + if (builder.hasServerInstrumentationFile()) { builder.instrument({ - entrypoint: worker_dest, - instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js` + entrypoint: `${server_dest}/index.js`, + instrumentation: `${server_dest}/instrumentation.server.js` }); } // _headers const headers_src = '_headers'; - const headers_dest = `${dest}/_headers`; + const headers_dest = `${client_dest}/_headers`; if (existsSync(headers_src)) { copyFileSync(headers_src, headers_dest); } @@ -140,7 +85,7 @@ export default function (options = {}) { // _redirects const redirects_src = '_redirects'; - const redirects_dest = `${dest}/_redirects`; + const redirects_dest = `${client_dest}/_redirects`; if (existsSync(redirects_src)) { copyFileSync(redirects_src, redirects_dest); } @@ -150,34 +95,137 @@ export default function (options = {}) { }); } - if (building_for_cloudflare_pages) { - // _routes.json - - // we need to add the source paths found in the `_redirects` file to the - // `_routes.json` file so that Cloudflare knows it shouldn't invoke the - // Worker but instead let the rules in the `_redirects` file take over. - /** @type {string[]} */ - let redirects = []; - if (existsSync(redirects_dest)) { - const redirect_rules = readFileSync(redirects_dest, 'utf8'); - redirects = parse_redirects(redirect_rules); - } - - writeFileSync( - `${dest}/_routes.json`, - JSON.stringify( - get_routes_json(builder, client_assets, redirects, options.routes ?? {}), - null, - '\t' - ) - ); - } else { - writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); - } + writeFileSync(`${client_dest}/.assetsignore`, '\n.vite', { flag: 'a' }); }, supports: { read: () => true, instrumentation: () => true + }, + vite: { + plugins: [ + // TODO: remove browser condition from resolve so that optimizeDeps works correctly with esm-env + { + name: 'vite-plugin-sveltekit-cloudflare-pre', + config(user_config, env) { + building = env.command === 'build'; + + user_config.environments ??= {}; + user_config.environments.ssr ??= {}; + user_config.environments.ssr.build ??= {}; + user_config.environments.ssr.build.rolldownOptions ??= {}; + + // prevents our server entry from clashing with Cloudflare's worker entry + if ( + typeof user_config.environments.ssr.build.rolldownOptions.input === 'object' && + 'index' in user_config.environments.ssr.build.rolldownOptions.input + ) { + user_config.environments.ssr.build.rolldownOptions.input.server = + user_config.environments.ssr.build.rolldownOptions.input.index; + delete user_config.environments.ssr.build.rolldownOptions.input.index; + } + }, + applyToEnvironment(environment) { + return environment.name === 'ssr'; + }, + resolveId: { + filter: { + id: [/^SERVER$/, /^MANIFEST$/] + }, + async handler(id, importer, options) { + if (importer !== default_worker || (id !== 'SERVER' && id !== 'MANIFEST')) return; + + if (!building) { + switch (id) { + case 'SERVER': + return '\0virtual:@sveltejs/kit/vite/environment/server'; + case 'MANIFEST': + return this.resolve( + 'virtual:@sveltejs/kit/vite/environment', + importer, + options + ); + } + } + + return { + id: `./${id.toLowerCase()}.js`, + external: true + }; + } + } + }, + cloudflare({ + ...options.vitePluginOptions, + configPath: options.vitePluginOptions?.configPath, + viteEnvironment: { + name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', + childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments + }, + config(user_config) { + // merge with the user's programmatic config + if (typeof options.vitePluginOptions?.config === 'function') { + options.vitePluginOptions?.config(user_config); + } else { + Object.assign(user_config, options.vitePluginOptions?.config); + } + + if (DEV) { + if (!user_config.assets?.binding) { + user_config.assets = { + binding: 'ASSETS' + }; + } + + if (!user_config.main) { + user_config.main = default_worker; + } + } else { + // TODO: only configure these if the user does not intend to deploy an assets-only worker + user_config.main = default_worker; + user_config.assets = { + binding: user_config.assets?.binding ?? 'ASSETS' + // no need to populate `directory` as the Cloudflare Vite plugin + // does that based on the client build input entry + }; + } + + if ( + !user_config.compatibility_flags.find( + (flag) => flag === 'nodejs_als' || flag === 'nodejs_compat' + ) + ) { + user_config.compatibility_flags.push('nodejs_als'); + } + + // TODO: warn on overridden config options? + + wrangler_config = user_config; + } + }), + { + name: 'vite-plugin-sveltekit-cloudflare-post', + config(user_config) { + // noop to prevent Cloudflare's fallback `buildApp` hook from + // running so that it doesn't build the client and server again + if (user_config.builder?.buildApp) { + user_config.builder.buildApp = async () => {}; + } + + user_config.environments ??= {}; + user_config.environments.ssr ??= {}; + user_config.environments.ssr.define ??= {}; + user_config.environments.ssr.define.__SVELTEKIT_CLOUDFLARE_ASSETS_BINDING__ = + JSON.stringify(wrangler_config.assets?.binding); + + // prevent Vite from resolving client svelte exports + const browser_condition = + user_config.environments.ssr.resolve?.conditions?.indexOf('browser'); + if (browser_condition && browser_condition >= 0) { + user_config.environments.ssr.resolve?.conditions?.splice(browser_condition, 1); + } + } + } + ] } }; } @@ -215,45 +263,3 @@ ${rules} # === END AUTOGENERATED SVELTE PRERENDERED REDIRECTS === `.trimEnd(); } - -/** - * @returns {string} - */ -function generate_assetsignore() { - // this comes from https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates-experimental/svelte/templates/static/.assetsignore - return ` -_worker.js -_routes.json -_headers -_redirects -`.trimEnd(); -} - -/** - * @param {string | undefined} config_file - * @returns {{ - * wrangler_config: import('wrangler').Unstable_Config, - * building_for_cloudflare_pages: boolean - * }} - */ -function validate_wrangler_config(config_file = undefined) { - const wrangler_config = unstable_readConfig({ config: config_file }); - - const building_for_cloudflare_pages = is_building_for_cloudflare_pages(wrangler_config); - - // we don't need to validate the config if we're building for Cloudflare Pages - // because the `main` and `assets` values cannot be changed there - if (!building_for_cloudflare_pages) { - validate_worker_settings(wrangler_config); - } - - return { - wrangler_config, - building_for_cloudflare_pages - }; -} - -/** @param {string} str */ -function posixify(str) { - return str.replace(/\\/g, '/'); -} diff --git a/packages/adapter-cloudflare/internal.d.ts b/packages/adapter-cloudflare/internal.d.ts index 6c79569f7f7f..f9d34a624d0f 100644 --- a/packages/adapter-cloudflare/internal.d.ts +++ b/packages/adapter-cloudflare/internal.d.ts @@ -10,3 +10,5 @@ declare module 'MANIFEST' { export const app_path: string; export const base_path: string; } + +declare const __SVELTEKIT_CLOUDFLARE_ASSETS_BINDING__: string; diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 1b77c76bb160..6b7235e7a2b0 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -23,13 +23,18 @@ "types": "./index.d.ts", "import": "./index.js" }, + "./worker": { + "types": "./src/exports.d.ts", + "import": "./src/exports.js" + }, "./package.json": "./package.json" }, "types": "index.d.ts", "files": [ "files", + "src/exports.js", + "src/exports.d.ts", "index.js", - "utils.js", "index.d.ts", "ambient.d.ts" ], @@ -44,7 +49,8 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@cloudflare/workers-types": "^4.20260219.0", + "@cloudflare/workers-types": "^4.20260408.1", + "esm-env": "^1.2.2", "worktop": "0.8.0-next.18" }, "devDependencies": { @@ -57,6 +63,6 @@ }, "peerDependencies": { "@sveltejs/kit": "^3.0.0", - "wrangler": "^4.67.0" + "@cloudflare/vite-plugin": "^1.31.1" } } diff --git a/packages/adapter-cloudflare/src/exports.d.ts b/packages/adapter-cloudflare/src/exports.d.ts new file mode 100644 index 000000000000..307c9f2f3220 --- /dev/null +++ b/packages/adapter-cloudflare/src/exports.d.ts @@ -0,0 +1 @@ +export const fetch: ExportedHandlerFetchHandler, any, any>; diff --git a/packages/adapter-cloudflare/src/exports.js b/packages/adapter-cloudflare/src/exports.js new file mode 100644 index 000000000000..4d3089e6ed72 --- /dev/null +++ b/packages/adapter-cloudflare/src/exports.js @@ -0,0 +1,6 @@ +import worker from '../files/worker.js'; + +/** @type {import('./exports.d.ts')['fetch']} */ +const fetch = worker.fetch; + +export { fetch }; diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js index f34f099bb759..250ca6a99bf0 100644 --- a/packages/adapter-cloudflare/src/worker.js +++ b/packages/adapter-cloudflare/src/worker.js @@ -1,3 +1,5 @@ +/// + import { Server } from 'SERVER'; import { manifest, prerendered, base_path } from 'MANIFEST'; import { env } from 'cloudflare:workers'; @@ -22,9 +24,9 @@ const initialized = server.init({ env, read: async (file) => { const url = `${origin}/${file}`; - const response = await /** @type {{ ASSETS: { fetch: typeof fetch } }} */ (env).ASSETS.fetch( - url - ); + const response = await /** @type {{ [key: string]: { fetch: typeof fetch } }} */ (env)[ + __SVELTEKIT_CLOUDFLARE_ASSETS_BINDING__ + ].fetch(url); if (!response.ok) { throw new Error( @@ -39,7 +41,7 @@ const initialized = server.init({ export default { /** * @param {Request} req - * @param {{ ASSETS: { fetch: typeof fetch } }} env + * @param {{ [key: string]: { fetch: typeof fetch } }} env * @param {ExecutionContext} ctx * @returns {Promise} */ @@ -84,7 +86,7 @@ export default { pathname === version_file || pathname.startsWith(immutable) ) { - res = await env.ASSETS.fetch(req); + res = await env[__SVELTEKIT_CLOUDFLARE_ASSETS_BINDING__].fetch(req); } else if (location && prerendered.has(location)) { // trailing slash redirect for prerendered pages if (search) location += search; diff --git a/packages/adapter-cloudflare/test/apps/pages/.gitignore b/packages/adapter-cloudflare/test/apps/pages/.gitignore deleted file mode 100644 index 1bd7b63de4b6..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -node_modules -/.svelte-kit -/.wrangler \ No newline at end of file diff --git a/packages/adapter-cloudflare/test/apps/pages/package.json b/packages/adapter-cloudflare/test/apps/pages/package.json deleted file mode 100644 index 0839ddd0a69f..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "test-cloudflare-pages", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "wrangler pages dev .svelte-kit/cloudflare --port 8787", - "prepare": "svelte-kit sync || echo ''", - "test": "playwright test" - }, - "devDependencies": { - "@sveltejs/kit": "workspace:^", - "@sveltejs/vite-plugin-svelte": "catalog:", - "server-side-dep": "file:server-side-dep", - "svelte": "catalog:", - "vite": "catalog:", - "wrangler": "catalog:" - }, - "type": "module" -} diff --git a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js deleted file mode 100644 index 33d36b651014..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js +++ /dev/null @@ -1 +0,0 @@ -export { config as default } from '../../utils.js'; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts deleted file mode 100644 index 0e1188e04a67..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function sum(a: number, b: number): number; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js deleted file mode 100644 index 568b90577d43..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('./index.js').sum} */ -export function sum(a, b) { - return a + b; -} diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json deleted file mode 100644 index 5b26c9d1855a..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "server-side-dep", - "version": "0.0.1", - "type": "module", - "exports": { - ".": { - "default": "./index.js", - "types": "./index.d.ts" - } - } -} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/app.html b/packages/adapter-cloudflare/test/apps/pages/src/app.html deleted file mode 100644 index d533c5e31716..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js deleted file mode 100644 index 0eeb7d398ffd..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js +++ /dev/null @@ -1,8 +0,0 @@ -// this tests that Wrangler can correctly resolve and bundle server-side dependencies -import { sum } from 'server-side-dep'; - -export function load() { - return { - sum: sum(1, 2) - }; -} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte deleted file mode 100644 index d5e339683387..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

Sum: {data.sum}

diff --git a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js deleted file mode 100644 index 20cd2b3ff5b8..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import adapter from '../../../index.js'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter() - } -}; - -export default config; diff --git a/packages/adapter-cloudflare/test/apps/pages/test/test.js b/packages/adapter-cloudflare/test/apps/pages/test/test.js deleted file mode 100644 index 0619c030bbc2..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/test/test.js +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('worker', async ({ page }) => { - await page.goto('/'); - await expect(page.locator('h1')).toContainText('Sum: 3'); -}); diff --git a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json deleted file mode 100644 index c2db702f9bb1..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "moduleResolution": "bundler" - }, - "extends": "./.svelte-kit/tsconfig.json" -} diff --git a/packages/adapter-cloudflare/test/apps/pages/vite.config.js b/packages/adapter-cloudflare/test/apps/pages/vite.config.js deleted file mode 100644 index 29ad08debe6a..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/vite.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - build: { - minify: false - }, - plugins: [sveltekit()] -}; - -export default config; diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index 0bf8a9db8c3a..fed0d7b53a2a 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -1,10 +1,5 @@ // we've moved the wrangler config away from the root of the project // to test that the adapter still resolves the paths correctly { - "$schema": "../node_modules/wrangler/config-schema.json", - "main": "../dist/index.js", - "assets": { - "directory": "../dist/public", - "binding": "ASSETS" - } + "$schema": "../../../../node_modules/wrangler/config-schema.json" } diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 4032805d1ec6..46d61300d85d 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "preview": "wrangler dev dist/index.js", + "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", @@ -16,8 +16,7 @@ "@sveltejs/vite-plugin-svelte": "catalog:", "server-side-dep": "file:server-side-dep", "svelte": "catalog:", - "vite": "catalog:", - "wrangler": "catalog:" + "vite": "catalog:" }, "type": "module" } diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js index 47717c4e0511..06a7b16f7cac 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js @@ -1,6 +1,8 @@ import { read } from '$app/server'; import file from './file.txt?url'; -export function GET() { - return read(file); +export async function GET() { + const asset = read(file); + const text = await asset.text(); + return new Response(text, asset); } diff --git a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js index 26cd6a965908..7af75d9f0e0b 100644 --- a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js @@ -4,7 +4,7 @@ import adapter from '../../../index.js'; const config = { kit: { adapter: adapter({ - config: 'config/wrangler.jsonc' + config: './config/wrangler.jsonc' }) } }; diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index 4c6f2a79a28d..d1ab81bc69e6 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -7,8 +7,7 @@ test('worker', async ({ page }) => { await expect(page.locator('h1')).toContainText('Sum: 3'); }); -// TODO: re-enable once we add the vite cloudflare plugin -test.skip('ctx', async ({ request }) => { +test('ctx', async ({ request }) => { const res = await request.get('/ctx'); expect(await res.text()).toBe('ctx works'); }); @@ -21,3 +20,9 @@ test('read from $app/server works', async ({ request }) => { const response = await request.get('/read'); expect(await response.text()).toBe(content); }); + +// TODO: test param matchers in dev-only + +// TODO: test prerendering a page that imports from cloudflare:workers works after build + +// TODO: test when paths.assets is configured after build diff --git a/packages/adapter-cloudflare/test/apps/workers/vite.config.js b/packages/adapter-cloudflare/test/apps/workers/vite.config.js index 29ad08debe6a..f0a66ea7eb94 100644 --- a/packages/adapter-cloudflare/test/apps/workers/vite.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/vite.config.js @@ -1,11 +1,17 @@ import { sveltekit } from '@sveltejs/kit/vite'; +import adapter from '../../../index.js'; /** @type {import('vite').UserConfig} */ const config = { build: { minify: false }, - plugins: [sveltekit()] + plugins: [sveltekit({ adapter: adapter({ vitePluginOptions: {} }) })], + server: { + fs: { + allow: ['../../../../kit'] + } + } }; export default config; diff --git a/packages/adapter-cloudflare/test/utils.js b/packages/adapter-cloudflare/test/utils.js index dcb3ce52c9d6..4ee245ff335d 100644 --- a/packages/adapter-cloudflare/test/utils.js +++ b/packages/adapter-cloudflare/test/utils.js @@ -9,7 +9,7 @@ export const config = { timeout: process.env.CI ? 45000 : 15000, webServer: { command: process.env.DEV ? 'pnpm dev' : 'pnpm build && pnpm preview', - port: process.env.DEV ? 5173 : 8787 + port: process.env.DEV ? 5173 : 4173 }, retries: process.env.CI ? 2 : number_from_env('KIT_E2E_RETRIES', 0), projects: [ diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 5a06e032a358..e4f95f2e2bb6 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -6,21 +6,20 @@ "module": "nodenext", "moduleResolution": "nodenext", "paths": { - "@sveltejs/kit": ["../kit/types/index"] + "@sveltejs/kit": ["../kit/types/index"], + "types": ["../kit/src/types/internal.d.ts"] }, // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", "lib": ["es2024"], - "types": ["node", "@cloudflare/workers-types"] + "types": ["node"] }, "include": [ "index.js", - "utils.js", - "utils.spec.js", "rolldown.config.js", "vitest.config.js", - "test/utils.js", "internal.d.ts", - "src/worker.js" + "test/utils.js", + "src/*" ] } diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js deleted file mode 100644 index 2b1c7c74b567..000000000000 --- a/packages/adapter-cloudflare/utils.js +++ /dev/null @@ -1,165 +0,0 @@ -import process from 'node:process'; - -/** - * @param {import('wrangler').Unstable_Config} wrangler_config - * @returns {boolean} - */ -export function is_building_for_cloudflare_pages(wrangler_config) { - if (process.env.CF_PAGES || wrangler_config.pages_build_output_dir) { - return true; - } - - if (!!process.env.WORKERS_CI || wrangler_config.main || wrangler_config.assets) { - return false; - } - - return true; -} - -/** - * @param {import('wrangler').Unstable_Config} wrangler_config - */ -export function validate_worker_settings(wrangler_config) { - const config_path = wrangler_config.configPath || 'your wrangler.jsonc file'; - - // we don't support workers sites - if (wrangler_config.site) { - throw new Error( - `You must remove all \`site\` keys in ${config_path}. Consult https://svelte.dev/docs/kit/adapter-cloudflare#Migrating-from-Workers-Sites` - ); - } - - // we need the `assets.directory` key so that the static assets are deployed - if ((wrangler_config.main || wrangler_config.assets) && !wrangler_config.assets?.directory) { - throw new Error( - `You must specify the \`assets.directory\` key in ${config_path}. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` - ); - } - - // we need the `assets.binding` key so that the Worker can access the static assets - if (wrangler_config.main && !wrangler_config.assets?.binding) { - throw new Error( - `You must specify the \`assets.binding\` key in ${config_path} before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` - ); - } - - // the user might have forgot the `main` key or should remove the `assets.binding` - // key to deploy static assets without a Worker - if (!wrangler_config.main && wrangler_config.assets?.binding) { - throw new Error( - `You must specify the \`main\` key in ${config_path} if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` - ); - } -} - -/** - * Extracts the redirect source from each line of a [_redirects](https://developers.cloudflare.com/pages/configuration/redirects/) - * file so we can exclude them in [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) - * to ensure the redirect is invoked instead of the Cloudflare Worker. - * @param {string} file_contents - * @returns {string[]} - */ -export function parse_redirects(file_contents) { - /** @type {string[]} */ - const redirects = []; - - for (const line of file_contents.split('\n')) { - const content = line.trim(); - if (!content || content.startsWith('#')) continue; - - const [pathname] = line.split(' '); - // pathnames with placeholders are not supported - if (!pathname || pathname.includes('/:')) { - throw new Error(`The following _redirects rule cannot be excluded by _routes.json: ${line}`); - } - redirects.push(pathname); - } - - return redirects; -} - -/** - * Generates the [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) - * file that dictates which routes invoke the Cloudflare Worker. - * @param {import('@sveltejs/kit').Builder} builder - * @param {string[]} client_assets - * @param {string[]} redirects - * @param {import('./index.js').AdapterOptions['routes']} routes - * @returns {import('./index.js').RoutesJSONSpec} - */ -export function get_routes_json(builder, client_assets, redirects, routes) { - const include = routes?.include ?? ['/*']; - let exclude = routes?.exclude ?? ['']; - - if (!Array.isArray(include) || !Array.isArray(exclude)) { - throw new Error('routes.include and routes.exclude must be arrays'); - } - - if (include?.length === 0) { - throw new Error('routes.include must contain at least one route'); - } - - if (include?.length > 100) { - throw new Error('routes.include must contain 100 or fewer routes'); - } - - /** @type {Set} */ - const transformed_rules = new Set(); - for (const rule of exclude) { - if (rule === '') { - transformed_rules.add(''); - transformed_rules.add(''); - transformed_rules.add(''); - transformed_rules.add(''); - } else { - transformed_rules.add(rule); - } - } - - /** @type {Set} */ - const excluded_routes = new Set(); - for (const rule of transformed_rules) { - if (rule === '') { - const app_path = builder.getAppPath(); - excluded_routes.add(`/${app_path}/version.json`); - excluded_routes.add(`/${app_path}/immutable/*`); - continue; - } - - if (rule === '') { - for (const file of client_assets) { - if (file.startsWith(`${builder.config.kit.appDir}/`)) continue; - excluded_routes.add(`${builder.config.kit.paths.base}/${file}`); - } - continue; - } - - if (rule === '') { - builder.prerendered.paths.forEach((path) => excluded_routes.add(path)); - continue; - } - - if (rule === '') { - redirects.forEach((path) => excluded_routes.add(path)); - continue; - } - - excluded_routes.add(rule); - } - exclude = Array.from(excluded_routes); - - const excess = include.length + exclude.length - 100; - if (excess > 0) { - builder.log.warn( - `Cloudflare Pages Functions' includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.` - ); - exclude.length -= excess; - } - - return { - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include, - exclude - }; -} diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js deleted file mode 100644 index 7d3755559376..000000000000 --- a/packages/adapter-cloudflare/utils.spec.js +++ /dev/null @@ -1,338 +0,0 @@ -import { describe, test, vi, expect } from 'vitest'; -import { - is_building_for_cloudflare_pages, - validate_worker_settings, - get_routes_json, - parse_redirects -} from './utils.js'; - -describe('detects Cloudflare Pages project', () => { - test('by default', () => { - expect( - is_building_for_cloudflare_pages(/** @type {import('wrangler').Unstable_Config} */ ({})) - ).toBe(true); - }); - - test('CF_PAGES environment variable', () => { - vi.stubEnv('CF_PAGES', '1'); - const result = is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({}) - ); - vi.unstubAllEnvs(); - expect(result).toBe(true); - }); - - test('empty Wrangler configuration file', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc' - }) - ) - ).toBe(true); - }); - - test('pages_build_output_dir config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - pages_build_output_dir: 'dist' - }) - ) - ).toBe(true); - }); -}); - -describe('detects Cloudflare Workers project', () => { - test('main config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js' - }) - ) - ).toBe(false); - }); - - test('assets config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - assets: { - directory: 'dist/assets' - } - }) - ) - ).toBe(false); - }); - - test('WORKERS_CI environment variable', () => { - vi.stubEnv('WORKERS_CI', '1'); - const result = is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({}) - ); - vi.unstubAllEnvs(); - expect(result).toBe(false); - }); -}); - -describe('validates Wrangler config', () => { - test('Worker and static assets', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js', - assets: { - directory: 'dist/assets', - binding: 'ASSETS' - } - }) - ) - ).not.toThrow(); - }); - - test('static assets only', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - assets: { - directory: 'dist/assets' - } - }) - ) - ).not.toThrow(); - }); - - test('missing `assets.directory` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js', - assets: { - binding: 'ASSETS' - } - }) - ) - ).toThrow( - `You must specify the \`assets.directory\` key in wrangler.jsonc. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` - ); - }); - - test('missing `assets.binding` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js', - assets: { - directory: 'dist/assets' - } - }) - ) - ).toThrow( - `You must specify the \`assets.binding\` key in wrangler.jsonc before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` - ); - }); - - test('missing `main` key or should remove `assets.binding` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - assets: { - directory: 'dist/assets', - binding: 'ASSETS' - } - }) - ) - ).toThrow( - `You must specify the \`main\` key in wrangler.jsonc if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` - ); - }); -}); - -test('ignores comments in _redirects file', () => { - const redirects = parse_redirects( - ` -# This is a comment -/home301 / 301 - # Indented comment -/blog/* https://blog.my.domain/:splat -`.trim() - ); - - expect(redirects).toEqual(['/home301', '/blog/*']); -}); - -test('parses _redirects file', () => { - const redirects = parse_redirects( - ` -/home301 / 301 -/notrailing/ /nottrailing 301 - -/blog/* https://blog.my.domain/:splat -`.trim() - ); - - expect(redirects).toEqual(['/home301', '/notrailing/', '/blog/*']); -}); - -test('generates a _routes.json file', () => { - const routes = get_routes_json( - { - getAppPath: () => 'base-path/_app', - config: { - kit: { - appDir: '_app', - paths: { - base: '/base-path', - assets: '', - relative: true - }, - alias: {}, - csrf: { - checkOrigin: true, - trustedOrigins: [] - }, - embedded: false, - files: { - src: 'src', - assets: 'static', - hooks: { - client: 'src/hooks.client.js', - server: 'src/hooks.server.js', - universal: 'src/hooks.js' - }, - lib: 'src/lib', - params: 'src/params', - routes: 'src/routes', - serviceWorker: 'src/service-worker.js', - appTemplate: 'src/app.html', - errorTemplate: 'src/error.html' - }, - inlineStyleThreshold: 0, - moduleExtensions: ['.js', '.ts'], - csp: { - mode: 'auto', - // @ts-ignore - directives: {}, - // @ts-ignore - reportOnly: {} - }, - env: { - dir: '.', - publicPrefix: 'PUBLIC_', - privatePrefix: '' - }, - outDir: '.svelte-kit' - } - }, - prerendered: { - paths: ['/base-path/prerendered'], - pages: new Map(), - assets: new Map(), - redirects: new Map() - } - }, - ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], - ['/base-path/redirect'], - undefined - ); - - expect(routes).toEqual({ - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include: ['/*'], - exclude: [ - '/base-path/_app/version.json', - '/base-path/_app/immutable/*', - '/base-path/robots.txt', - '/base-path/prerendered', - '/base-path/redirect' - ] - }); -}); - -test('truncates excess _routes.json exclude rules', () => { - const routes = get_routes_json( - { - // @ts-ignore - log: { - warn: console.warn - }, - getAppPath: () => 'base-path/_app', - config: { - kit: { - appDir: '_app', - paths: { - base: '/base-path', - assets: '', - relative: true - }, - alias: {}, - csrf: { - checkOrigin: true, - trustedOrigins: [] - }, - embedded: false, - files: { - src: 'src', - assets: 'static', - hooks: { - client: 'src/hooks.client.js', - server: 'src/hooks.server.js', - universal: 'src/hooks.js' - }, - lib: 'src/lib', - params: 'src/params', - routes: 'src/routes', - serviceWorker: 'src/service-worker.js', - appTemplate: 'src/app.html', - errorTemplate: 'src/error.html' - }, - inlineStyleThreshold: 0, - moduleExtensions: ['.js', '.ts'], - csp: { - mode: 'auto', - // @ts-ignore - directives: {}, - // @ts-ignore - reportOnly: {} - }, - env: { - dir: '.', - publicPrefix: 'PUBLIC_', - privatePrefix: '' - }, - outDir: '.svelte-kit' - } - }, - prerendered: { - paths: Array.from({ length: 100 }, (_, i) => `/base-path/blog/post/${i + 1}`), - pages: new Map(), - assets: new Map(), - redirects: new Map() - } - }, - ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], - [], - undefined - ); - - expect(routes).toEqual({ - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include: ['/*'], - exclude: [ - '/base-path/_app/version.json', - '/base-path/_app/immutable/*', - '/base-path/robots.txt' - ].concat(Array.from({ length: 96 }, (_, i) => `/base-path/blog/post/${i + 1}`)) - }); -}); diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index a4b654459fa3..22e185370b63 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -43,6 +43,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo /** @type {import('types').ServerInternalModule} */ const internal = await import(pathToFileURL(`${out}/server/internal.js`).href); + // TODO: the cloudflare vite plugin reserves the index.js filename for the worker so we need to make this adaptable or refactor prerenderering to use the correct environment /** @type {import('types').ServerModule} */ const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 9dd507fcbdf9..5b5002ee518a 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -107,6 +107,12 @@ type UnpackValidationError = ? undefined // needs to be undefined, because void will corrupt union type : T; +export interface ManifestGenerationOptions { + /** A relative path to the base directory of the server build output */ + relativePath: string; + routes?: RouteDefinition[]; +} + /** * This object is passed to the `adapt` function of adapters. * It contains various methods and properties that are useful for adapting the app. @@ -150,9 +156,8 @@ export interface Builder { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: ManifestGenerationOptions) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -313,17 +318,6 @@ export interface Cookies { serialize: (name: string, value: string, opts: import('cookie').SerializeOptions) => string; } -/** - * A collection of functions that influence the environment during dev, build and prerendering - */ -export interface Emulator { - /** - * A function that is called with the current route `config` and `prerender` option - * and returns an `App.Platform` object - */ - platform?(details: { config: any; prerender: PrerenderOption }): MaybePromise; -} - export interface KitConfig { /** * Your [adapter](https://svelte.dev/docs/kit/adapters) is run when executing `vite build`. It determines how the output is converted for different platforms. diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 96edd3f73b6a..3b3317ab240a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -13,7 +13,7 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; import { is_chrome_devtools_request, not_found } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; -import { sveltekit_manifest_data } from '../module_ids.js'; +import { sveltekit_dev_server, sveltekit_manifest_data } from '../module_ids.js'; import { to_fs } from '../filesystem.js'; // vite-specifc queries that we should skip handling for css urls @@ -60,7 +60,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return; } - void invalidate_module(vite, sveltekit_manifest_data); + invalidate_module(vite, sveltekit_manifest_data); } update_manifest(); @@ -148,6 +148,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base; dev_environment.assets = assets; + invalidate_module(vite, sveltekit_dev_server); const asset_server = sirv(svelte_config.kit.files.assets, { dev: true, @@ -182,6 +183,10 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { next(); }); + const inline_styles_pathname = new RegExp( + `\\/${svelte_config.kit.appDir.replaceAll('/', '\\\\/')}\\/inline-styles\\/\\d+` + ); + return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -196,22 +201,16 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { dev_environment.remote_address = req.socket.remoteAddress; // Vite's base middleware strips out the base path. Restore it - let original_url = req.url; + const original_url = req.url; req.url = req.originalUrl; try { const base = `${vite.config.server.https ? 'https' : 'http'}://${ req.headers[':authority'] || req.headers.host }`; + dev_environment.origin = base; + invalidate_module(vite, sveltekit_manifest_data); - let decoded = decodeURI(new URL(base + req.url).pathname); - - // requests to _app/immutable during development are fetchable dev - // environments trying to read the filesystem - const immutable = `/${svelte_config.kit.appDir}/immutable`; - if (decoded.startsWith(immutable)) { - decoded = decoded.slice(immutable.length); - original_url = original_url?.slice(immutable.length); - } + const decoded = decodeURI(new URL(base + req.url).pathname); const file = posixify( path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) @@ -236,6 +235,16 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return not_found(req, res, svelte_config.kit.paths.base); } + if (decoded.match(inline_styles_pathname)) { + const urls = new URL(base + req.url).searchParams.getAll('urls'); + const styles = await get_inline_css(vite, urls); + res.writeHead(200, { + 'content-type': 'application/json' + }); + res.end(JSON.stringify(styles)); + return; + } + if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); @@ -397,6 +406,8 @@ export function invalidate_module(vite, id) { } } +// TODO: move this to an endpoint + /** * @param {import('vite').ViteDevServer} vite * @param {string[]} urls diff --git a/packages/kit/src/exports/vite/dev/ssr-manifest.js b/packages/kit/src/exports/vite/dev/ssr-manifest.js index 1e5be0f3176c..ebc87c637947 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -1,13 +1,13 @@ import { server_assets } from '__sveltekit/server-assets'; import { remotes } from '__sveltekit/remotes'; -import { env, kit, manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { env, kit, manifest_data, mime_types, origin } from '__sveltekit/manifest-data'; import { to_fs } from '../filesystem.js'; import { compact } from '../../../utils/array.js'; import { join } from '../../../utils/path.js'; export { env }; - -export const basePath = kit.paths.base; +export const base_path = kit.paths.base; +export const prerendered = new Set(); export const manifest = { appDir: kit.appDir, @@ -102,33 +102,32 @@ export const manifest = { // in dev we inline all styles to avoid FOUC. this gets populated lazily so that // components/stylesheets loaded via import() during `load` are included - const event = `sveltekit:inline-styles-node-${i}-response`; result.inline_styles = async () => { - if (!import.meta.hot) throw new Error('hmr must be enabled in the dev environment'); - - const { promise, resolve } = Promise.withResolvers(); - - /** @param {Record} styles */ - const listener = async (styles) => { - import.meta.hot?.off(event, listener); - const importing_styles = Object.entries(styles).map( - async ([dep_url, inline_css_url]) => { - return [ - dep_url, - await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) - ]; - } + // we can't use import.meta.hot here because workerd doesn't like that + // it would resolve from a different request context + const inline_styles_endpoint = new URL(`/${kit.appDir}/inline-styles/${result.index}`, origin); + for (const url of urls) { + inline_styles_endpoint.searchParams.append('urls', url); + } + + const response = await fetch(inline_styles_endpoint); + if (!response.ok) { + throw new Error( + `Failed to fetch inline styles for node ${i}: ${response.status} ${response.statusText}. This should never happen` ); - resolve(Object.fromEntries(await Promise.all(importing_styles))); - }; + } + + /** @type {Record} */ + const styles = await response.json(); - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles-request', { - urls, - node: result.index + const importing_styles = Object.entries(styles).map(async ([dep_url, inline_css_url]) => { + return [ + dep_url, + await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) + ]; }); - return promise; + return Object.fromEntries(await Promise.all(importing_styles)); }; return result; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 571d9e0727c3..c4eef2982127 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -21,7 +21,7 @@ import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '.. import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module, get_inline_css } from './dev/index.js'; +import { dev, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -567,7 +567,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; /** @type {Record} */ - let server_assets = {}; + let server_assets; /** * Allows us to access the filesystem from an environment that doesn't have `node:fs` @@ -577,7 +577,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { name: 'vite-plugin-sveltekit-server-filesystem', apply: 'serve', applyToEnvironment(environment) { - return environment.name !== 'client' && environment.name !== 'serviceWorker'; + return environment.config.consumer === 'server'; }, configureServer() { server_assets = {}; @@ -612,160 +612,56 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {import('vite').Plugin} */ - const plugin_virtual_modules = { - name: 'vite-plugin-sveltekit-virtual-modules', + const dev_ssr_manifest = path.join(import.meta.dirname, 'dev/ssr-manifest.js'); + const dev_server_entry = path.join(import.meta.dirname, 'dev/server.js'); + /** @type {import('vite').Plugin} */ + const plugin_dev_ssr = { + name: 'vite-plugin-sveltekit-dev-ssr', + apply: 'serve', applyToEnvironment(environment) { - return environment.name !== 'serviceWorker'; + return environment.config.consumer === 'server'; }, - - resolveId(id, importer) { - if (id === '__sveltekit/manifest') { - return `${kit.outDir}/generated/client-optimized/app.js`; - } - - // If importing from a service-worker, only allow $service-worker & $env/static/public, but none of the other virtual modules. - // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly. - // Transitive imports will be caught during the build. - // TODO move this logic to plugin_guard. add a filter to this resolveId when doing so - if (importer) { - const parsed_importer = path.parse(importer); - - const importer_is_service_worker = - parsed_importer.dir === parsed_service_worker.dir && - parsed_importer.name === parsed_service_worker.name; - - if (importer_is_service_worker && id !== '$service-worker' && id !== '$env/static/public') { - throw new Error( - `Cannot import ${normalize_id( - id, - normalized_lib, - normalized_cwd - )} into service-worker code. Only the modules $service-worker and $env/static/public are available in service workers.` - ); + resolveId: { + filter: { + id: [prefixRegex('virtual:@sveltejs/kit'), prefixRegex('__sveltekit/')] + }, + handler(id) { + // these virtual modules which are public paths must have the virtual prefix + // otherwise the bundler complains about not being able to find them in our + // package.json exports because we already have @sveltejs/kit/vite exported + if (id === 'virtual:@sveltejs/kit/vite/environment') { + return dev_ssr_manifest; } - } - // treat $env/static/[public|private] as virtual - if (id.startsWith('$env/') || id === '$service-worker') { - // ids with :$ don't work with reverse proxies like nginx - return `\0virtual:${id.substring(1)}`; - } - - if (id === '__sveltekit/remote') { - return `${runtime_directory}/client/remote-functions/index.js`; - } - - // these virtual modules which are public paths should have the virtual prefix - // otherwise the bundler complains about not being able to find them based - // on the fact that we have @sveltejs/kit/vite in our package.json exports list - if (id === 'virtual:@sveltejs/kit/vite/environment') { - return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); - } - - if (id === '__sveltekit/dev-server-entry') { - return server_instrumentation_file - ? sveltekit_traced - : path.join(import.meta.dirname, 'dev/server.js'); - } + if (id === 'virtual:@sveltejs/kit/vite/environment/server') { + return `\0${id}`; + } - if (id === 'virtual:@sveltejs/kit/vite/environment/server') { - return `\0${id}`; - } + if (id === '__sveltekit/dev-server-entry') { + return server_instrumentation_file ? sveltekit_traced : dev_server_entry; + } - if (id.startsWith('__sveltekit/') && id !== '__sveltekit/dev-server-entry') { - return `\0virtual:${id}`; + if (id.startsWith('__sveltekit/')) { + return `\0virtual:${id}`; + } } }, load: { filter: { id: [ - exactRegex(env_static_private), - exactRegex(env_static_public), - exactRegex(env_dynamic_private), - exactRegex(env_dynamic_public), - exactRegex(service_worker), - exactRegex(sveltekit_environment), - exactRegex(sveltekit_server), - exactRegex(sveltekit_manifest_data), exactRegex(sveltekit_server_assets), exactRegex(sveltekit_remotes), - exactRegex(sveltekit_traced), - exactRegex(sveltekit_dev_server) + exactRegex(sveltekit_manifest_data), + exactRegex(sveltekit_dev_server), + exactRegex(sveltekit_traced) ] }, handler(id) { - switch (id) { - case env_static_private: - return create_static_module('$env/static/private', env.private); - - case env_static_public: - return create_static_module('$env/static/public', env.public); - - case env_dynamic_private: - return create_dynamic_module( - 'private', - vite_config_env.command === 'serve' ? env.private : undefined, - root - ); - - case env_dynamic_public: { - // populate `$env/dynamic/public` from `window` - if (this.environment.config.consumer === 'client') { - const global = is_build - ? `globalThis.__sveltekit_${version_hash}` - : 'globalThis.__sveltekit_dev'; - return `export const env = ${global}.env;`; - } - - return create_dynamic_module( - 'public', - vite_config_env.command === 'serve' ? env.public : undefined, - root - ); - } - - case service_worker: - return create_service_worker_module(svelte_config); - - case sveltekit_environment: { - const { version } = svelte_config.kit; - - return dedent` - export const version = ${s(version.name)}; - export let building = false; - export let prerendering = false; - - export function set_building() { - building = true; - } - - export function set_prerendering() { - prerendering = true; - } - `; - } - - case sveltekit_server: { - return dedent` - export let read_implementation = null; - - export let manifest = null; - - export function set_read_implementation(fn) { - read_implementation = fn; - } - - export function set_manifest(_) { - manifest = _; - } - `; - } + if (!dev_environment) return; + switch (id) { case sveltekit_server_assets: { - if (!dev_environment) return; - return dedent` export const server_assets = { ${Object.entries(server_assets) @@ -780,8 +676,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } case sveltekit_remotes: { - if (!dev_environment) return; - return dedent` export const remotes = ${s(remotes)}; @@ -792,8 +686,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } case sveltekit_manifest_data: { - if (!dev_environment) return; - const { manifest_data, env } = dev_environment; return dedent` @@ -816,12 +708,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { }, paths: ${s(kit.paths)} } + + export const origin = ${s(dev_environment.origin)} `; } case sveltekit_dev_server: { - if (!dev_environment) return; - const runtime_base = get_runtime_base(root); const adapter = svelte_config.kit.adapter; @@ -888,6 +780,139 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; + /** @type {import('vite').Plugin} */ + const plugin_virtual_modules = { + name: 'vite-plugin-sveltekit-virtual-modules', + + applyToEnvironment(environment) { + return environment.name !== 'serviceWorker'; + }, + + resolveId(id, importer) { + if (id === '__sveltekit/manifest') { + return `${kit.outDir}/generated/client-optimized/app.js`; + } + + // If importing from a service-worker, only allow $service-worker & $env/static/public, but none of the other virtual modules. + // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly. + // Transitive imports will be caught during the build. + // TODO move this logic to plugin_guard. add a filter to this resolveId when doing so + if (importer) { + const parsed_importer = path.parse(importer); + + const importer_is_service_worker = + parsed_importer.dir === parsed_service_worker.dir && + parsed_importer.name === parsed_service_worker.name; + + if (importer_is_service_worker && id !== '$service-worker' && id !== '$env/static/public') { + throw new Error( + `Cannot import ${normalize_id( + id, + normalized_lib, + normalized_cwd + )} into service-worker code. Only the modules $service-worker and $env/static/public are available in service workers.` + ); + } + } + + // treat $env/static/[public|private] as virtual + if (id.startsWith('$env/') || id === '$service-worker') { + // ids with :$ don't work with reverse proxies like nginx + return `\0virtual:${id.substring(1)}`; + } + + if (id === '__sveltekit/remote') { + return `${runtime_directory}/client/remote-functions/index.js`; + } + + if (id.startsWith('__sveltekit/')) { + return `\0virtual:${id}`; + } + }, + load: { + filter: { + id: [ + exactRegex(env_static_private), + exactRegex(env_static_public), + exactRegex(env_dynamic_private), + exactRegex(env_dynamic_public), + exactRegex(service_worker), + exactRegex(sveltekit_environment), + exactRegex(sveltekit_server) + ] + }, + handler(id) { + switch (id) { + case env_static_private: + return create_static_module('$env/static/private', env.private); + + case env_static_public: + return create_static_module('$env/static/public', env.public); + + case env_dynamic_private: + return create_dynamic_module( + 'private', + vite_config_env.command === 'serve' ? env.private : undefined, + root + ); + + case env_dynamic_public: { + // populate `$env/dynamic/public` from `window` + if (this.environment.config.consumer === 'client') { + const global = is_build + ? `globalThis.__sveltekit_${version_hash}` + : 'globalThis.__sveltekit_dev'; + return `export const env = ${global}.env;`; + } + + return create_dynamic_module( + 'public', + vite_config_env.command === 'serve' ? env.public : undefined, + root + ); + } + + case service_worker: + return create_service_worker_module(svelte_config); + + case sveltekit_environment: { + const { version } = svelte_config.kit; + + return dedent` + export const version = ${s(version.name)}; + export let building = false; + export let prerendering = false; + + export function set_building() { + building = true; + } + + export function set_prerendering() { + prerendering = true; + } + `; + } + + case sveltekit_server: { + return dedent` + export let read_implementation = null; + + export let manifest = null; + + export function set_read_implementation(fn) { + read_implementation = fn; + } + + export function set_manifest(_) { + manifest = _; + } + `; + } + } + } + } + }; + /** @type {Map>} */ const import_map = new Map(); const server_only_pattern = /.*\.server\..+/; @@ -1305,10 +1330,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {(payload: { urls: string[]; node: number; }) => Promise} */ - let handle_inline_styles; - /** @type {(error: Error) => void} */ - let handle_ssr_load_module; + /** @type {() => Promise} */ + let finalise; /** @type {import('vite').Plugin} */ const plugin_compile = { @@ -1594,47 +1617,18 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ configureServer(vite) { - vite.environments.ssr.hot.off('sveltekit:inline-styles-request', handle_inline_styles); - vite.environments.ssr.hot.off('sveltekit:ssr-load-module', handle_ssr_load_module); + vite.environments.ssr.hot.off('sveltekit:ssr-load-module', display_ssr_error_on_client); manifest_data = sync.all(svelte_config, vite_config_env.mode, root).manifest_data; - // other properties will be populated during the `dev` function + // other properties will be populated after running the `dev` function below dev_environment = /** @type {import('types').DevEnvironment} */ ({ vite, env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''), manifest_data }); - handle_inline_styles ??= async ({ urls, node }) => { - vite.environments.ssr.hot.send( - `sveltekit:inline-styles-node-${node}-response`, - await get_inline_css(vite, urls) - ); - }; - vite.environments.ssr.hot.on('sveltekit:inline-styles-request', handle_inline_styles); - - handle_ssr_load_module ??= (err) => { - const msg = buildErrorMessage(err, [ - styleText('red', `Internal server error: ${err.message}`) - ]); - - if (!vite.config.logger.hasErrorLogged(err)) { - vite.config.logger.error(msg, { error: err }); - } - - vite.ws.send({ - type: 'error', - err: { - ...err, - // these properties are non-enumerable and will - // not be serialized unless we explicitly include them - message: err.message, - stack: err.stack ?? '' - } - }); - }; - vite.environments.ssr.hot.on('sveltekit:ssr-load-module', handle_ssr_load_module); + vite.environments.ssr.hot.on('sveltekit:ssr-load-module', display_ssr_error_on_client); return dev(vite, vite_config, svelte_config, root, dev_environment); }, @@ -1644,6 +1638,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver */ configurePreviewServer(vite) { + // TODO: ensure `paths.assets` is correctly proxied + + if (svelte_config.kit.adapter?.vite?.plugins) return; + return preview(vite, vite_config, svelte_config); }, @@ -1957,50 +1955,69 @@ function kit({ svelte_config, adapter_in_vite_config }) { })};\n` ); - if (service_worker_entry_file) { - if (kit.paths.assets) { - throw new Error('Cannot use service worker alongside config.kit.paths.assets'); - } + // defer the adapt step to run after any buildApp hooks the adapter might have + finalise = async () => { + // defer creating the service worker too because other plugins might build + // the client environment again and overwrite our service worker which + // outputs to the same directory + if (service_worker_entry_file) { + if (kit.paths.assets) { + throw new Error('Cannot use service worker alongside config.kit.paths.assets'); + } - log.info('Building service worker'); + log.info('Building service worker'); - builder.environments.serviceWorker.config.define = - builder.environments.client.config.define; - builder.environments.serviceWorker.config.resolve.alias = [ - ...get_config_aliases(kit, vite_config.root) - ]; - builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => { - return { - runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname` + builder.environments.serviceWorker.config.define = + builder.environments.client.config.define; + builder.environments.serviceWorker.config.resolve.alias = [ + ...get_config_aliases(kit, vite_config.root) + ]; + builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => { + return { + runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname` + }; }; - }; - - await builder.build(builder.environments.serviceWorker); - } - - console.log( - `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.` - ); - if (kit.adapter) { - const { adapt } = await import('../../core/adapt/index.js'); - await adapt( - svelte_config, - build_data, - metadata, - prerendered, - prerender_results.prerender_map, - log, - remotes, - vite_config - ); - } else { - console.log(styleText(['bold', 'yellow'], '\nNo adapter specified')); + await builder.build(builder.environments.serviceWorker); + } - const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters'); console.log( - `See ${link} to learn how to configure your app to run on the platform of your choosing` + `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.` ); + + if (kit.adapter) { + const { adapt } = await import('../../core/adapt/index.js'); + await adapt( + svelte_config, + build_data, + metadata, + prerendered, + prerender_results.prerender_map, + log, + remotes, + vite_config + ); + } else { + console.log(styleText(['bold', 'yellow'], '\nNo adapter specified')); + + const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters'); + console.log( + `See ${link} to learn how to configure your app to run on the platform of your choosing` + ); + } + }; + } + }; + + /** @type {import('vite').Plugin} */ + const plugin_adapter = { + name: 'vite-plugin-sveltekit-adapter', + buildApp: { + // this will run after any buildApp hooks provided by other Vite plugins + // see https://vite.dev/guide/api-environment-frameworks#environments-during-build + order: 'post', + async handler() { + await finalise(); } } }; @@ -2009,10 +2026,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { plugin_setup, plugin_remote, plugin_server_filesystem, + plugin_dev_ssr, plugin_virtual_modules, process.env.TEST !== 'true' ? plugin_guard : undefined, plugin_service_worker, - plugin_compile + plugin_compile, + plugin_adapter ].filter((p) => !!p); } @@ -2082,3 +2101,26 @@ function create_service_worker_module(config) { export const version = ${s(config.kit.version.name)}; `; } + +/** @type {(error: Error) => void} */ +function display_ssr_error_on_client(err) { + const vite = dev_environment?.vite; + if (!vite) return; + + const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); + + if (!vite.config.logger.hasErrorLogged(err)) { + vite.config.logger.error(msg, { error: err }); + } + + vite.ws.send({ + type: 'error', + err: { + ...err, + // these properties are non-enumerable and will + // not be serialized unless we explicitly include them + message: err.message, + stack: err.stack ?? '' + } + }); +} diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index a67b25fc87e5..a7cd802fd0fa 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -29,7 +29,6 @@ declare module '__sveltekit/server' { } declare module '__sveltekit/manifest-data' { - // eslint-disable-next-line no-duplicate-imports import { Asset, PageNode, RouteData } from 'types'; export const env: Record; @@ -52,6 +51,7 @@ declare module '__sveltekit/manifest-data' { matchers: string[][]; assets: Asset[]; }; + export const origin: string; } declare module '__sveltekit/server-assets' { diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 62344d30b1a9..0e427f60d34e 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -152,6 +152,8 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { export const manifest: SSRManifest; export const env: Record; + export const base_path: string; + export const prerendered: Set; } declare module 'virtual:@sveltejs/kit/vite/environment/server' { diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 87b5b3fe5f08..34b06589c655 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -692,10 +692,16 @@ export interface RequestStore { export interface DevEnvironment { vite: ViteDevServer; + /** used to construct the SSR manifest */ manifest_data: ManifestData; + /** environment variables loaded according to Vite's mode, etc. */ env: Record; + /** where app assets are served from */ assets: string; + /** used to populate `event.getClientAddress()` */ remote_address: string | undefined; + /** address of the development server */ + origin: string; } export * from '../exports/index.js'; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8fae925d3e91..7322042c565c 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -82,6 +82,12 @@ declare module '@sveltejs/kit' { ? undefined // needs to be undefined, because void will corrupt union type : T; + export interface ManifestGenerationOptions { + /** A relative path to the base directory of the server build output */ + relativePath: string; + routes?: RouteDefinition[]; + } + /** * This object is passed to the `adapt` function of adapters. * It contains various methods and properties that are useful for adapting the app. @@ -125,9 +131,8 @@ declare module '@sveltejs/kit' { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: ManifestGenerationOptions) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -287,17 +292,6 @@ declare module '@sveltejs/kit' { serialize: (name: string, value: string, opts: import('cookie').SerializeOptions) => string; } - /** - * A collection of functions that influence the environment during dev, build and prerendering - */ - export interface Emulator { - /** - * A function that is called with the current route `config` and `prerender` option - * and returns an `App.Platform` object - */ - platform?(details: { config: any; prerender: PrerenderOption }): MaybePromise; - } - export interface KitConfig { /** * Your [adapter](https://svelte.dev/docs/kit/adapters) is run when executing `vite build`. It determines how the output is converted for different platforms. @@ -3641,6 +3635,8 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { export const manifest: SSRManifest; export const env: Record; + export const base_path: string; + export const prerendered: Set; } declare module 'virtual:@sveltejs/kit/vite/environment/server' { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f23dfebb1ea6..25bb7f7fe9dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,9 +105,6 @@ catalogs: vitest: specifier: ^4.1.1 version: 4.1.2 - wrangler: - specifier: ^4.67.0 - version: 4.79.0 importers: @@ -155,15 +152,18 @@ importers: packages/adapter-cloudflare: dependencies: + '@cloudflare/vite-plugin': + specifier: ^1.31.1 + version: 1.31.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.81.0(@cloudflare/workers-types@4.20260408.1)) '@cloudflare/workers-types': - specifier: ^4.20260219.0 - version: 4.20260401.1 + specifier: ^4.20260408.1 + version: 4.20260408.1 + esm-env: + specifier: ^1.2.2 + version: 1.2.2 worktop: specifier: 0.8.0-next.18 version: 0.8.0-next.18 - wrangler: - specifier: ^4.67.0 - version: 4.79.0(@cloudflare/workers-types@4.20260401.1) devDependencies: '@playwright/test': specifier: 'catalog:' @@ -184,27 +184,6 @@ importers: specifier: 'catalog:' version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-playwright@4.1.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3)) - packages/adapter-cloudflare/test/apps/pages: - devDependencies: - '@sveltejs/kit': - specifier: workspace:^ - version: link:../../../../kit - '@sveltejs/vite-plugin-svelte': - specifier: 'catalog:' - version: 7.0.0(svelte@5.53.12)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3)) - server-side-dep: - specifier: file:server-side-dep - version: file:packages/adapter-cloudflare/test/apps/pages/server-side-dep - svelte: - specifier: 'catalog:' - version: 5.53.12 - vite: - specifier: 'catalog:' - version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3) - wrangler: - specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260401.1) - packages/adapter-cloudflare/test/apps/workers: devDependencies: '@sveltejs/kit': @@ -222,9 +201,6 @@ importers: vite: specifier: 'catalog:' version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3) - wrangler: - specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260401.1) packages/adapter-netlify: dependencies: @@ -1534,38 +1510,44 @@ packages: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20260329.1': - resolution: {integrity: sha512-oyDXYlPBuGXKkZ85+M3jFz0/qYmvA4AEURN8USIGPDCR5q+HFSRwywSd9neTx3Wi7jhey2wuYaEpD3fEFWyWUA==} + '@cloudflare/vite-plugin@1.31.1': + resolution: {integrity: sha512-vw4pOS8FmODdCeWjAG0gO4OyZ4Bb4GXlET/taaLDRm7gC5uGcH5XRPoTUJPYrs54LbWZxi3e2iWXX3JLRv4Rfg==} + peerDependencies: + vite: ^6.1.0 || ^7.0.0 || ^8.0.0 + wrangler: ^4.81.0 + + '@cloudflare/workerd-darwin-64@1.20260405.1': + resolution: {integrity: sha512-EbmdBcmeIGogKG4V1odSWQe7z4rHssUD4iaXv0cXA22/MFrzH3iQT0R+FJFyhucGtih/9B9E+6j0QbSQD8xT3w==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260329.1': - resolution: {integrity: sha512-++ZxVa3ovzYeDLEG6zMqql9gzZAG8vak6ZSBQgprGKZp7akr+GKTpw9f3RrMP552NSi3gTisroLobrrkPBtYLQ==} + '@cloudflare/workerd-darwin-arm64@1.20260405.1': + resolution: {integrity: sha512-r44r418bOQtoP+Odu+L/BQM9q5cRSXRd1N167PgZQIo4MlqzTwHO4L0wwXhxbcV/PF46rrQre/uTFS8R0R+xSQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260329.1': - resolution: {integrity: sha512-kkeywAgIHwbqHkVILqbj/YkfbrA6ARbmutjiYzZA2MwMSfNXlw6/kedAKOY8YwcymZIgepx3YTIPnBP50pOotw==} + '@cloudflare/workerd-linux-64@1.20260405.1': + resolution: {integrity: sha512-Aaq3RWnaTCzMBo77wC8fjOx+SFdO/rlcXa6HAf+PJs51LyMISFOBCJKqSlS6Irphen0WHHxFKPHUO9bjfj8g2g==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260329.1': - resolution: {integrity: sha512-eYBN20+B7XOUSWEe0mlqkMUbfLoIKjKZnpqQiSxnLbL72JKY0D/KlfN/b7RVGLpewB7i8rTrwTNr0szCKnZzSQ==} + '@cloudflare/workerd-linux-arm64@1.20260405.1': + resolution: {integrity: sha512-Lbp9Z2wiMzy3Sji3YwMHK5WDlejsH3jF4swAFEv7+jIf3NowZHga3GzwTypNRmcwnfz/XrqQ7Hc0Ul9OoU/lCw==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260329.1': - resolution: {integrity: sha512-5R+/oxrDhS9nL3oA3ZWtD6ndMOqm7RfKknDNxLcmYW5DkUu7UH3J/s1t/Dz66iFePzr5BJmE7/8gbmve6TjtZQ==} + '@cloudflare/workerd-windows-64@1.20260405.1': + resolution: {integrity: sha512-FhE0kt93kj5JnSPVqi4BAXpQQENyKnuSOoJLd35mkMMGhtPrwv5EsReJdck0S8hUocCBlb+U0RmP8ta6k41HjQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260401.1': - resolution: {integrity: sha512-tKBeV/ySfJjbO0qMKkFrstHDdWzZHAcW4vCpO5QaqjB/667y9lhZt9gZyTKeJ0gluIBwpeQ/efBjqRLqpkgw9g==} + '@cloudflare/workers-types@4.20260408.1': + resolution: {integrity: sha512-kE1tKfHUyIldsj3ea2XEqvLRHkDwc83YM7nar6SS5+cj81IoAFR/OZNDwZWHb6vx+pC31PBJGtROlfZzsgxudQ==} '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} @@ -4658,8 +4640,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@4.20260329.0: - resolution: {integrity: sha512-+G+1YFVeuEpw/gZZmUHQR7IfzJV+DDGvnSl0yXzhgvHh8Nbr8Go5uiWIwl17EyZ1Uors3FKUMDUyU6+ejeKZOw==} + miniflare@4.20260405.0: + resolution: {integrity: sha512-tpr4XdWMq7zFdsHH+CS0XS47nQzlRZH0rMJ1vobOZbkrs3cIj7qbD40ON616hDnzHxwqwB2qKHzmmuj6oRisSQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -5207,9 +5189,6 @@ packages: engines: {node: '>=10'} hasBin: true - server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: - resolution: {directory: packages/adapter-cloudflare/test/apps/pages/server-side-dep, type: directory} - server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: resolution: {directory: packages/adapter-cloudflare/test/apps/workers/server-side-dep, type: directory} @@ -5806,8 +5785,8 @@ packages: resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} engines: {node: '>= 12.0.0'} - workerd@1.20260329.1: - resolution: {integrity: sha512-+ifMv3uBuD33ee7pan5n8+sgVxm2u5HnbgfXzHKwMNTKw86znqBJSnJoBqtP88+2T5U2Lu11xXUt+khPYioXwQ==} + workerd@1.20260405.1: + resolution: {integrity: sha512-bSaRWCv9iO8/FWpgZRjHLGZLolX5s1AErRSYaTECMMHOZKuCbl2+ehnSyc+ZZ/70y+9owADmN6HoYEWvBlJdYw==} engines: {node: '>=16'} hasBin: true @@ -5815,12 +5794,12 @@ packages: resolution: {integrity: sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw==} engines: {node: '>=12'} - wrangler@4.79.0: - resolution: {integrity: sha512-NMinIdB1pXIqdk+NLw4+RjzB7K5z4+lWMxhTxFTfZomwJu3Pm6N+kZ+a66D3nI7w0oCjsdv/umrZVmSHCBp2cg==} + wrangler@4.81.0: + resolution: {integrity: sha512-9fLPDuDcb8Nu6iXrl5E3HGYt3TVhQr/UvqtTvWr9Nl1X7PlQrmWMwQCfSioqN8VHYyQCyESV5jQsoKg8Sx+sEA==} engines: {node: '>=20.3.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260329.1 + '@cloudflare/workers-types': ^4.20260405.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -6110,28 +6089,41 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1)': + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260405.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260329.1 + workerd: 1.20260405.1 + + '@cloudflare/vite-plugin@1.31.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.81.0(@cloudflare/workers-types@4.20260408.1))': + dependencies: + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260405.1) + miniflare: 4.20260405.0 + unenv: 2.0.0-rc.24 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.3) + wrangler: 4.81.0(@cloudflare/workers-types@4.20260408.1) + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - workerd - '@cloudflare/workerd-darwin-64@1.20260329.1': + '@cloudflare/workerd-darwin-64@1.20260405.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260329.1': + '@cloudflare/workerd-darwin-arm64@1.20260405.1': optional: true - '@cloudflare/workerd-linux-64@1.20260329.1': + '@cloudflare/workerd-linux-64@1.20260405.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260329.1': + '@cloudflare/workerd-linux-arm64@1.20260405.1': optional: true - '@cloudflare/workerd-windows-64@1.20260329.1': + '@cloudflare/workerd-windows-64@1.20260405.1': optional: true - '@cloudflare/workers-types@4.20260401.1': {} + '@cloudflare/workers-types@4.20260408.1': {} '@colors/colors@1.6.0': {} @@ -9227,12 +9219,12 @@ snapshots: min-indent@1.0.1: {} - miniflare@4.20260329.0: + miniflare@4.20260405.0: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 undici: 7.24.4 - workerd: 1.20260329.1 + workerd: 1.20260405.1 ws: 8.18.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -9822,8 +9814,6 @@ snapshots: semver@7.7.4: {} - server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: {} - server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: {} sharp@0.34.5: @@ -10364,31 +10354,31 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.9.0 - workerd@1.20260329.1: + workerd@1.20260405.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260329.1 - '@cloudflare/workerd-darwin-arm64': 1.20260329.1 - '@cloudflare/workerd-linux-64': 1.20260329.1 - '@cloudflare/workerd-linux-arm64': 1.20260329.1 - '@cloudflare/workerd-windows-64': 1.20260329.1 + '@cloudflare/workerd-darwin-64': 1.20260405.1 + '@cloudflare/workerd-darwin-arm64': 1.20260405.1 + '@cloudflare/workerd-linux-64': 1.20260405.1 + '@cloudflare/workerd-linux-arm64': 1.20260405.1 + '@cloudflare/workerd-windows-64': 1.20260405.1 worktop@0.8.0-next.18: dependencies: mrmime: 2.0.0 regexparam: 3.0.0 - wrangler@4.79.0(@cloudflare/workers-types@4.20260401.1): + wrangler@4.81.0(@cloudflare/workers-types@4.20260408.1): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1) + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260405.1) blake3-wasm: 2.1.5 esbuild: 0.27.3 - miniflare: 4.20260329.0 + miniflare: 4.20260405.0 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260329.1 + workerd: 1.20260405.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260401.1 + '@cloudflare/workers-types': 4.20260408.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 239193cc6b8b..3584486c54c6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -52,7 +52,6 @@ catalog: valibot: ^1.1.0 vite: ^8.0.2 vitest: ^4.1.1 - wrangler: ^4.67.0 catalogs: # these show up in the ci.yml like `vite: 'beta'`, etc. vite-baseline: