chore: force `custom-renderer` custom condition

custom-condition
paoloricciuti 3 days ago
parent 970991bf46
commit aeddeb5632

@ -1,5 +1,13 @@
<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->
### custom_renderer_unavailable_on_server
```
You need to specify a custom condition to use a custom renderer in a non-browser environment such as Node. Add the `custom-renderer` resolve condition (eg. run Node with `--conditions=custom-renderer`).
If you're using a bundler, add `custom-renderer` to its list of resolve conditions instead.
```
### experimental_async_required
```

@ -1,3 +1,9 @@
## custom_renderer_unavailable_on_server
> You need to specify a custom condition to use a custom renderer in a non-browser environment such as Node. Add the `custom-renderer` resolve condition (eg. run Node with `--conditions=custom-renderer`).
>
> If you're using a bundler, add `custom-renderer` to its list of resolve conditions instead.
## experimental_async_required
> Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true`

@ -22,6 +22,7 @@
"exports": {
".": {
"types": "./types/index.d.ts",
"custom-renderer": "./src/index-client.js",
"worker": "./src/index-server.js",
"browser": "./src/index-client.js",
"default": "./src/index-server.js"
@ -72,13 +73,16 @@
"default": "./src/internal/flags/tracing.js"
},
"./internal/flags/custom-renderer": {
"default": "./src/internal/flags/custom-renderer.js"
"custom-renderer": "./src/internal/flags/custom-renderer.js",
"browser": "./src/internal/flags/custom-renderer.js",
"default": "./src/internal/flags/custom-renderer-server.js"
},
"./internal/server": {
"default": "./src/internal/server/index.js"
},
"./legacy": {
"types": "./types/index.d.ts",
"custom-renderer": "./src/legacy/legacy-client.js",
"worker": "./src/legacy/legacy-server.js",
"browser": "./src/legacy/legacy-client.js",
"default": "./src/legacy/legacy-server.js"
@ -89,6 +93,7 @@
},
"./reactivity": {
"types": "./types/index.d.ts",
"custom-renderer": "./src/reactivity/index-client.js",
"worker": "./src/reactivity/index-server.js",
"browser": "./src/reactivity/index-client.js",
"default": "./src/reactivity/index-server.js"
@ -99,7 +104,9 @@
},
"./renderer": {
"types": "./types/index.d.ts",
"default": "./src/renderer/index.js"
"custom-renderer": "./src/renderer/index.js",
"browser": "./src/renderer/index.js",
"default": "./src/renderer/index-server.js"
},
"./server": {
"types": "./types/index.d.ts",
@ -107,6 +114,7 @@
},
"./store": {
"types": "./types/index.d.ts",
"custom-renderer": "./src/store/index-client.js",
"worker": "./src/store/index-server.js",
"browser": "./src/store/index-client.js",
"default": "./src/store/index-server.js"

@ -0,0 +1,10 @@
import * as e from '../shared/errors.js';
// This module is injected by the compiler into every component when the custom
// renderer feature is enabled. It is only ever imported as a package specifier
// (`svelte/internal/flags/custom-renderer`) from compiled client output, which
// must run where Svelte's client build is available — i.e. with the `browser`
// or `custom-renderer` resolve condition set. If we end up here, the component
// is running in a server context without that condition, so fail fast with the
// same descriptive error as `svelte/renderer`.
e.custom_renderer_unavailable_on_server();

@ -2,6 +2,26 @@
import { DEV } from 'esm-env';
/**
* You need to specify a custom condition to use a custom renderer in a non-browser environment such as Node. Add the `custom-renderer` resolve condition (eg. run Node with `--conditions=custom-renderer`).
*
* If you're using a bundler, add `custom-renderer` to its list of resolve conditions instead.
* @returns {never}
*/
export function custom_renderer_unavailable_on_server() {
if (DEV) {
const error = new Error(`custom_renderer_unavailable_on_server\nYou need to specify a custom condition to use a custom renderer in a non-browser environment such as Node. Add the \`custom-renderer\` resolve condition (eg. run Node with \`--conditions=custom-renderer\`).
If you're using a bundler, add \`custom-renderer\` to its list of resolve conditions instead.\nhttps://svelte.dev/e/custom_renderer_unavailable_on_server`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/custom_renderer_unavailable_on_server`);
}
}
/**
* Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true`
* @param {string} name

@ -0,0 +1,18 @@
import * as e from '../internal/shared/errors.js';
/**
* Server-side stub for `svelte/renderer`. Custom renderers rely on Svelte's
* client build (the same one selected by the `browser` export condition), which
* is not available in a server context. Calling `createRenderer` here throws a
* descriptive error pointing users at the `custom-renderer` resolve condition
* rather than letting them fail later with a cryptic `mount` error.
*
* The parameter mirrors the real `createRenderer` signature so this stub is a
* drop-in replacement; it is intentionally unused.
*
* @param {unknown} [_renderer]
* @returns {never}
*/
export function createRenderer(_renderer) {
e.custom_renderer_unavailable_on_server();
}

@ -0,0 +1,25 @@
// @vitest-environment node
// This suite runs in a dedicated Vitest project (see the root `vitest.config.js`
// `projects` array) that resolves `svelte` using only the server/`default`
// export condition — i.e. it simulates running a custom renderer in Node
// *without* the required `custom-renderer` (or `browser`) resolve condition.
// In that situation Svelte should fail fast with a descriptive error rather
// than letting the user hit a cryptic `mount` failure later on.
import { expect, test } from 'vitest';
test('`createRenderer` from `svelte/renderer` throws a descriptive error', async () => {
const { createRenderer } = await import('svelte/renderer');
// @ts-expect-error - we are testing that this throws, so we don't care about the type signature
expect(() => createRenderer({})).toThrow(/custom_renderer_unavailable_on_server/);
});
test('importing the custom renderer flag module throws a descriptive error', async () => {
// the compiler injects `import 'svelte/internal/flags/custom-renderer'` into
// every component when the feature is enabled, so this must fail loudly too
await expect(import('svelte/internal/flags/custom-renderer')).rejects.toThrow(
/custom_renderer_unavailable_on_server/
);
});

@ -4,28 +4,42 @@ import { configDefaults, defineConfig } from 'vitest/config';
const pkg = JSON.parse(fs.readFileSync('packages/svelte/package.json', 'utf8'));
export default defineConfig({
resolve: {
alias: [
{
find: /^svelte\/?/,
customResolver: (id, importer) => {
// For some reason this turns up as "undefined" instead of "svelte/"
const exported = pkg.exports[id === 'undefined' ? '.' : id.replace('undefined', './')];
if (!exported) return;
/**
* Resolves `svelte` / `svelte/*` imports against the package's `exports` map.
*
* @param {boolean} force_server When `true`, always resolve to the server
* (`default`) condition. This is used by the `custom-renderer-server` project
* to simulate running in Node *without* the `custom-renderer`/`browser`
* resolve condition. Otherwise the client (`browser`) build is preferred,
* except for compiled SSR output (`_output/server`).
*/
function svelte_alias(force_server = false) {
return [
{
find: /^svelte\/?/,
/**
* @param {string} id
* @param {string | undefined} importer
*/
customResolver: (id, importer) => {
// For some reason this turns up as "undefined" instead of "svelte/"
const exported = pkg.exports[id === 'undefined' ? '.' : id.replace('undefined', './')];
if (!exported) return;
// When running the server version of the Svelte files,
// we also want to use the server export of the Svelte package
return path.resolve(
'packages/svelte',
importer?.includes('_output/server')
? exported.default
: exported.browser ?? exported.default
);
}
// When running the server version of the Svelte files,
// we also want to use the server export of the Svelte package
const use_server = force_server || importer?.includes('_output/server');
return path.resolve(
'packages/svelte',
use_server ? exported.default : exported.browser ?? exported.default
);
}
]
},
}
];
}
export default defineConfig({
test: {
dir: '.',
reporters: ['dot'],
@ -34,18 +48,42 @@ export default defineConfig({
// slow enough under vitest 4's deeper async stacks (and CI's slower workers)
// to overrun the 5s default.
testTimeout: 30_000,
include: [
'packages/svelte/**/*.test.ts',
'packages/svelte/tests/*/test.ts',
'packages/svelte/tests/runtime-browser/test-ssr.ts'
],
exclude: [...configDefaults.exclude, '**/samples/**'],
coverage: {
provider: 'v8',
reporter: ['lcov', 'html'],
include: ['packages/svelte/src/**'],
reportsDirectory: 'coverage',
reportOnFailure: true
}
},
projects: [
{
extends: true,
resolve: { alias: svelte_alias() },
test: {
name: 'svelte',
include: [
'packages/svelte/**/*.test.ts',
'packages/svelte/tests/*/test.ts',
'packages/svelte/tests/runtime-browser/test-ssr.ts'
],
exclude: [
...configDefaults.exclude,
'**/samples/**',
// runs in its own project with the server resolve condition
'packages/svelte/tests/custom-renderers-server/**'
]
}
},
{
extends: true,
// resolve `svelte` as if no `custom-renderer`/`browser` condition was set
resolve: { alias: svelte_alias(true) },
test: {
name: { label: 'custom-renderer-server', color: 'red' },
environment: 'node',
include: ['packages/svelte/tests/custom-renderers-server/test.ts']
}
}
]
}
});

Loading…
Cancel
Save