361 lines
10 KiB
JavaScript
361 lines
10 KiB
JavaScript
/** @import { ModuleCompileOptions, ValidatedModuleCompileOptions, CompileOptions, ValidatedCompileOptions } from '#compiler' */
|
|
import * as e from './errors.js';
|
|
import * as w from './warnings.js';
|
|
|
|
/**
|
|
* @template [Input=any]
|
|
* @template [Output=Input]
|
|
* @typedef {(input: Input, keypath: string) => Required<Output>} Validator
|
|
*/
|
|
|
|
const common_options = {
|
|
filename: string('(unknown)'),
|
|
|
|
// default to process.cwd() where it exists to replicate svelte4 behavior (and make Deno work with this as well)
|
|
// see https://github.com/sveltejs/svelte/blob/b62fc8c8fd2640c9b99168f01b9d958cb2f7574f/packages/svelte/src/compiler/compile/Component.js#L211
|
|
/* eslint-disable */
|
|
rootDir: string(
|
|
typeof process !== 'undefined'
|
|
? process.cwd?.()
|
|
: // @ts-expect-error
|
|
typeof Deno !== 'undefined'
|
|
? // @ts-expect-error
|
|
Deno.cwd()
|
|
: undefined
|
|
),
|
|
/* eslint-enable */
|
|
|
|
dev: boolean(false),
|
|
|
|
generate: validator('client', (input, keypath) => {
|
|
if (input === 'dom' || input === 'ssr') {
|
|
warn_once(w.options_renamed_ssr_dom);
|
|
return input === 'dom' ? 'client' : 'server';
|
|
}
|
|
|
|
// TODO deprecate `false` in favour of `analyze`/`analyzeModule` https://github.com/sveltejs/svelte-octane/issues/655
|
|
if (input !== 'client' && input !== 'server' && input !== false) {
|
|
throw_error(`${keypath} must be "client", "server" or false`);
|
|
}
|
|
|
|
return input;
|
|
}),
|
|
|
|
warningFilter: fun(() => true),
|
|
|
|
experimental: object({
|
|
async: boolean(false)
|
|
})
|
|
};
|
|
|
|
const component_options = {
|
|
accessors: deprecate(w.options_deprecated_accessors, boolean(false)),
|
|
|
|
/** @type {Validator<'injected' | 'external' | ((options: { filename: string }) => 'injected' | 'external'), (options: { filename: string }) => 'injected' | 'external'>} */
|
|
css: parametric(
|
|
/** @type {(options: { filename: string }) => 'injected' | 'external'} */ (() => 'external'),
|
|
(input) => {
|
|
if (input === true || input === false) {
|
|
throw_error(
|
|
'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true'
|
|
);
|
|
}
|
|
if (input === 'none') {
|
|
throw_error(
|
|
'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.'
|
|
);
|
|
}
|
|
|
|
if (input !== 'external' && input !== 'injected') {
|
|
throw_error(`css should be either "external" (default, recommended) or "injected"`);
|
|
}
|
|
|
|
return /** @type {'external' | 'injected'} */ (input);
|
|
}
|
|
),
|
|
|
|
cssHash: fun(({ css, filename, hash }) => {
|
|
return `svelte-${hash(filename === '(unknown)' ? css : filename ?? css)}`;
|
|
}),
|
|
|
|
// TODO this is a sourcemap option, would be good to put under a sourcemap namespace
|
|
cssOutputFilename: string(undefined),
|
|
|
|
/** @type {Validator<boolean | ((options: { filename: string }) => boolean), (options: { filename: string }) => boolean>} */
|
|
customElement: parametric(
|
|
/** @type {(options: { filename: string }) => boolean} */ (() => false),
|
|
(input, keypath) => {
|
|
if (typeof input !== 'boolean') {
|
|
throw_error(`${keypath} should be true or false`);
|
|
}
|
|
|
|
return /** @type {boolean} */ (input);
|
|
}
|
|
),
|
|
|
|
discloseVersion: boolean(true),
|
|
|
|
immutable: deprecate(w.options_deprecated_immutable, boolean(false)),
|
|
|
|
legacy: removed(
|
|
'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead'
|
|
),
|
|
|
|
compatibility: object({
|
|
componentApi: list([4, 5], 5)
|
|
}),
|
|
|
|
loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout),
|
|
|
|
name: string(undefined),
|
|
|
|
namespace: list(['html', 'mathml', 'svg']),
|
|
|
|
modernAst: boolean(false),
|
|
|
|
outputFilename: string(undefined),
|
|
|
|
preserveComments: boolean(false),
|
|
|
|
fragments: list(['html', 'tree']),
|
|
|
|
preserveWhitespace: boolean(false),
|
|
|
|
/** @type {Validator<boolean | undefined | (() => boolean | undefined), () => boolean | undefined>} */
|
|
runes: parametric(() => /** @type {boolean | undefined} */ (undefined)),
|
|
|
|
hmr: boolean(false),
|
|
|
|
sourcemap: validator(undefined, (input) => {
|
|
// Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map,
|
|
// so there's no good way to check type validity here
|
|
return input;
|
|
}),
|
|
|
|
enableSourcemap: warn_removed(w.options_removed_enable_sourcemap),
|
|
|
|
hydratable: warn_removed(w.options_removed_hydratable),
|
|
|
|
format: removed(
|
|
'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' +
|
|
'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)'
|
|
),
|
|
|
|
tag: removed(
|
|
'The tag option has been removed in Svelte 5. Use `<svelte:options customElement="tag-name" />` inside the component instead. ' +
|
|
'If that does not solve your use case, please open an issue on GitHub with details.'
|
|
),
|
|
|
|
sveltePath: removed(
|
|
'The sveltePath option has been removed in Svelte 5. ' +
|
|
'If this option was crucial for you, please open an issue on GitHub with your use case.'
|
|
),
|
|
|
|
// These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194),
|
|
// but with new TypeScript compilation modes strictly separating types it's not necessary anymore
|
|
errorMode: removed(
|
|
'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
|
|
'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
|
|
),
|
|
|
|
varsReport: removed(
|
|
'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' +
|
|
'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead'
|
|
)
|
|
};
|
|
|
|
export const validate_module_options =
|
|
/** @type {Validator<ModuleCompileOptions, ValidatedModuleCompileOptions>} */ (
|
|
object({
|
|
...common_options,
|
|
...Object.fromEntries(Object.keys(component_options).map((key) => [key, () => {}]))
|
|
})
|
|
);
|
|
|
|
export const validate_component_options =
|
|
/** @type {Validator<CompileOptions, ValidatedCompileOptions>} */ (
|
|
object({
|
|
...common_options,
|
|
...component_options
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @param {string} msg
|
|
* @returns {Validator}
|
|
*/
|
|
function removed(msg) {
|
|
return (input) => {
|
|
if (input !== undefined) {
|
|
e.options_removed(null, msg);
|
|
}
|
|
return /** @type {any} */ (undefined);
|
|
};
|
|
}
|
|
|
|
const warned = new Set();
|
|
|
|
/** @param {(node: null) => void} fn */
|
|
function warn_once(fn) {
|
|
if (!warned.has(fn)) {
|
|
warned.add(fn);
|
|
fn(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {(node: null) => void} fn
|
|
* @returns {Validator}
|
|
*/
|
|
function warn_removed(fn) {
|
|
return (input) => {
|
|
if (input !== undefined) warn_once(fn);
|
|
return /** @type {any} */ (undefined);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {(node: null) => void} fn
|
|
* @param {Validator} validator
|
|
* @returns {Validator}
|
|
*/
|
|
function deprecate(fn, validator) {
|
|
return (input, keypath) => {
|
|
if (input !== undefined) warn_once(fn);
|
|
return validator(input, keypath);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, Validator>} children
|
|
* @param {boolean} [allow_unknown]
|
|
* @returns {Validator}
|
|
*/
|
|
function object(children, allow_unknown = false) {
|
|
return (input, keypath) => {
|
|
/** @type {Record<string, any>} */
|
|
const output = {};
|
|
|
|
if ((input && typeof input !== 'object') || Array.isArray(input)) {
|
|
throw_error(`${keypath} should be an object`);
|
|
}
|
|
|
|
for (const key in input) {
|
|
if (!(key in children)) {
|
|
if (allow_unknown) {
|
|
output[key] = input[key];
|
|
} else {
|
|
e.options_unrecognised(null, `${keypath ? `${keypath}.${key}` : key}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const key in children) {
|
|
const validator = children[key];
|
|
output[key] = validator(input && input[key], keypath ? `${keypath}.${key}` : key);
|
|
}
|
|
|
|
return output;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {any} fallback
|
|
* @param {(value: any, keypath: string) => any} fn
|
|
* @returns {Validator}
|
|
*/
|
|
function validator(fallback, fn) {
|
|
return (input, keypath) => {
|
|
return input === undefined ? fallback : fn(input, keypath);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string | undefined} fallback
|
|
* @param {boolean} allow_empty
|
|
* @returns {Validator}
|
|
*/
|
|
function string(fallback, allow_empty = true) {
|
|
return validator(fallback, (input, keypath) => {
|
|
if (typeof input !== 'string') {
|
|
throw_error(`${keypath} should be a string, if specified`);
|
|
}
|
|
|
|
if (!allow_empty && input === '') {
|
|
throw_error(`${keypath} cannot be empty`);
|
|
}
|
|
|
|
return input;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {boolean | undefined} fallback
|
|
* @returns {Validator}
|
|
*/
|
|
function boolean(fallback) {
|
|
return validator(fallback, (input, keypath) => {
|
|
if (typeof input !== 'boolean') {
|
|
throw_error(`${keypath} should be true or false, if specified`);
|
|
}
|
|
return input;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {Array<boolean | string | number>} options
|
|
* @returns {Validator}
|
|
*/
|
|
function list(options, fallback = options[0]) {
|
|
return validator(fallback, (input, keypath) => {
|
|
if (!options.includes(input)) {
|
|
// prettier-ignore
|
|
const msg = options.length > 2
|
|
? `${keypath} should be one of ${options.slice(0, -1).map(input => `"${input}"`).join(', ')} or "${options[options.length - 1]}"`
|
|
: `${keypath} should be either "${options[0]}" or "${options[1]}"`;
|
|
|
|
throw_error(msg);
|
|
}
|
|
return input;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {(...args: any) => any} fallback
|
|
* @returns {Validator}
|
|
*/
|
|
function fun(fallback) {
|
|
return validator(fallback, (input, keypath) => {
|
|
if (typeof input !== 'function') {
|
|
throw_error(`${keypath} should be a function, if specified`);
|
|
}
|
|
return input;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @template {(...args: any[]) => any} F
|
|
* @param {F} fallback
|
|
* @param {(value: unknown, keypath: string) => ReturnType<F>} [normalize]
|
|
* @returns {Validator}
|
|
*/
|
|
function parametric(fallback, normalize = (value) => /** @type {ReturnType<F>} */ (value)) {
|
|
return validator(fallback, (input, keypath) => {
|
|
if (typeof input === 'function') {
|
|
/** @type {(...args: Parameters<F>) => ReturnType<F>} */
|
|
const normalized = (...args) => normalize(input(...args), keypath);
|
|
|
|
return /** @type {F} */ (/** @type {unknown} */ (normalized));
|
|
}
|
|
|
|
/** @type {(...args: Parameters<F>) => ReturnType<F>} */
|
|
const normalized = (..._args) => normalize(input, keypath);
|
|
|
|
return /** @type {F} */ (/** @type {unknown} */ (normalized));
|
|
});
|
|
}
|
|
|
|
/** @param {string} msg */
|
|
function throw_error(msg) {
|
|
e.options_invalid_value(null, msg);
|
|
}
|