svelte
Version:
Cybernetically enhanced web apps
336 lines (281 loc) • 9.14 kB
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 = {
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)
};
export const validate_module_options =
/** @type {Validator<ModuleCompileOptions, ValidatedModuleCompileOptions>} */ (
object({
...common
})
);
export const validate_component_options =
/** @type {Validator<CompileOptions, ValidatedCompileOptions>} */ (
object({
...common,
accessors: deprecate(w.options_deprecated_accessors, boolean(false)),
css: validator('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 input;
}),
cssHash: fun(({ css, hash }) => {
return `svelte-${hash(css)}`;
}),
// TODO this is a sourcemap option, would be good to put under a sourcemap namespace
cssOutputFilename: string(undefined),
customElement: boolean(false),
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),
preserveWhitespace: boolean(false),
runes: boolean(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'
)
})
);
/**
* @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 {number} fallback
* @returns {Validator}
*/
function number(fallback) {
return validator(fallback, (input, keypath) => {
if (typeof input !== 'number') {
throw_error(`${keypath} should be a number, if specified`);
}
return input;
});
}
/**
* @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 {string[]} fallback
* @returns {Validator}
*/
function string_array(fallback) {
return validator(fallback, (input, keypath) => {
if (input && !Array.isArray(input)) {
throw_error(`${keypath} should be a string array, if specified`);
}
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;
});
}
/** @param {string} msg */
function throw_error(msg) {
e.options_invalid_value(null, msg);
}