chore: make option accept a function

pull/18058/head
paoloricciuti 3 months ago
parent b4cf3d0a71
commit c189bdcfe3

@ -30,7 +30,7 @@ export function compile(source, options) {
const {
customElement: customElementOptions,
customRenderer,
customRenderer: custom_renderer,
...parsed_options
} = parsed.options || {};
@ -43,7 +43,7 @@ export function compile(source, options) {
runes: 'runes' in parsed_options ? () => parsed_options.runes : validated.runes,
experimental: {
...validated.experimental,
...(customRenderer !== undefined ? { customRenderer } : {})
...(custom_renderer !== undefined ? { customRenderer: () => custom_renderer } : {})
}
};

@ -467,6 +467,7 @@ export function analyze_component(root, source, options) {
const custom_element_from_option = options.customElement({ filename: options.filename });
const css = options.css({ filename: options.filename });
const custom_renderer = options.experimental.customRenderer({ filename: options.filename });
const custom_element = options.customElementOptions ?? custom_element_from_option;
const is_custom_element = !!options.customElementOptions || custom_element_from_option;
@ -529,6 +530,7 @@ export function analyze_component(root, source, options) {
event_directive_node: null,
uses_event_attributes: false,
custom_element,
custom_renderer,
inject_styles: css === 'injected' || is_custom_element,
accessors:
is_custom_element ||

@ -60,7 +60,7 @@ export function Attribute(node, context) {
}
// we can't delegate event handlers in a non dom environment
if (!context.state.options.experimental.customRenderer) {
if (!context.state.analysis.custom_renderer) {
node.metadata.delegated =
parent?.type === 'RegularElement' && can_delegate_event(node.name.slice(2));
}

@ -112,7 +112,7 @@ export function RegularElement(node, context) {
// We mark the subtree as dynamic so parent elements properly include the child init code
if (
(is_customizable_select_element(node) || node.name === 'selectedcontent') &&
!context.state.options.experimental.customRenderer
!context.state.analysis.custom_renderer
) {
// Mark the element's own fragment as dynamic so it's not treated as static
node.fragment.metadata.dynamic = true;

@ -163,7 +163,7 @@ export function client_component(analysis, options) {
scopes: analysis.module.scopes,
is_instance: false,
hoisted: [
...custom_renderer_imports(options.experimental.customRenderer),
...custom_renderer_imports(analysis.custom_renderer),
...analysis.instance_body.hoisted
],
node: /** @type {any} */ (null), // populated by the root node
@ -406,7 +406,7 @@ export function client_component(analysis, options) {
);
}
if (options.experimental.customRenderer) {
if (analysis.custom_renderer) {
component_block.body.unshift(
b.var('$$pop_renderer', b.call('$.push_renderer', b.id('$renderer')))
);
@ -585,7 +585,7 @@ export function client_component(analysis, options) {
// disclose version attach the svelte version to `window` which is not guaranteed
// to be a thing in custom renderers environments
if (options.discloseVersion && !options.experimental.customRenderer) {
if (options.discloseVersion && !analysis.custom_renderer) {
body.unshift(b.imports([], 'svelte/internal/disclose-version'));
}

@ -36,7 +36,7 @@ function build_locations(nodes) {
*/
export function transform_template(state, namespace, flags = 0) {
// custom renderers needs a tree to work because there's no template element we can use
const tree = state.options.fragments === 'tree' || !!state.options.experimental.customRenderer;
const tree = state.options.fragments === 'tree' || !!state.analysis.custom_renderer;
const expression = tree ? state.template.as_tree() : state.template.as_html();

@ -41,7 +41,7 @@ export function RegularElement(node, context) {
const is_html =
context.state.metadata.namespace === 'html' &&
node.name !== 'svg' &&
!context.state.options.experimental.customRenderer;
!context.state.analysis.custom_renderer;
const name = is_html ? node.name.toLowerCase() : node.name;
context.state.template.push_element(name, node.start, is_html);
@ -51,8 +51,7 @@ export function RegularElement(node, context) {
}
// we never treat elements as custom element in custom renderers, since we don't want to apply special handling to them (e.g. class merging)
const is_custom_element =
is_custom_element_node(node) && !context.state.options.experimental.customRenderer;
const is_custom_element = is_custom_element_node(node) && !context.state.analysis.custom_renderer;
// cloneNode is faster, but it does not instantiate the underlying class of the
// custom element until the template is connected to the dom, which would
@ -258,7 +257,7 @@ export function RegularElement(node, context) {
if (name !== 'class' || value) {
context.state.template.set_prop(attribute.name, value === true ? '' : value);
}
} else if (name === 'autofocus' && !context.state.options.experimental.customRenderer) {
} else if (name === 'autofocus' && !context.state.analysis.custom_renderer) {
let { value } = build_attribute_value(attribute.value, context);
context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
} else if (name === 'class') {
@ -280,7 +279,7 @@ export function RegularElement(node, context) {
name,
value,
attributes,
!!context.state.options.experimental.customRenderer
!!context.state.analysis.custom_renderer
);
(has_state ? context.state.update : context.state.init).push(b.stmt(update));
@ -357,7 +356,7 @@ export function RegularElement(node, context) {
const empty_string = value.type === 'Literal' && value.value === '';
if (!empty_string) {
if (context.state.options.experimental.customRenderer) {
if (context.state.analysis.custom_renderer) {
// custom renderers need to use the method to invoke the renderer
context.state.template.push_text([
{
@ -377,10 +376,7 @@ export function RegularElement(node, context) {
);
}
}
} else if (
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
} else if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
// For <option>, <optgroup>, or <select> elements with rich content, we need to branch based on browser support.
// Modern browsers preserve rich HTML in options, older browsers strip it to text only.
// We create a separate template for the rich content and append it to the element.

@ -83,7 +83,7 @@ export function process_children(nodes, initial, is_element, context) {
if (has_state && !within_bound_contenteditable) {
context.state.update.push(update);
} else {
if (context.state.options.experimental.customRenderer) {
if (context.state.analysis.custom_renderer) {
// custom renderers need to use the method to invoke the renderer
context.state.init.push(update);
} else {

@ -110,10 +110,7 @@ export function RegularElement(node, context) {
const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
if (
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
rest.push(b.true);
}
@ -160,10 +157,7 @@ export function RegularElement(node, context) {
const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
if (
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
rest.push(b.true);
}
@ -201,7 +195,7 @@ export function RegularElement(node, context) {
if (
(name === 'optgroup' || name === 'select') &&
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
!context.state.analysis.custom_renderer
) {
state.template.push(b.literal('<!>'));
}

@ -100,6 +100,10 @@ export interface ComponentAnalysis extends Analysis {
* or if not present a boolean which corresponds to the compiler option value
*/
custom_element: boolean | AST.SvelteOptions['customElement'];
/**
* The resolved custom renderer module path, or undefined if not using a custom renderer
*/
custom_renderer: string | undefined;
/** If `true`, should append styles through JavaScript */
inject_styles: boolean;
reactive_statements: Map<LabeledStatement, ReactiveStatement>;

@ -241,7 +241,7 @@ export interface ModuleCompileOptions {
/**
* Path to a module that exports the custom renderer to use. When this is truthy templating mode will also be automatically set to `functional`
*/
customRenderer?: string;
customRenderer?: string | ((options: { filename: string }) => string | undefined);
};
}
@ -249,6 +249,9 @@ export interface ModuleCompileOptions {
export type ValidatedModuleCompileOptions = Omit<Required<ModuleCompileOptions>, 'rootDir'> & {
rootDir: ModuleCompileOptions['rootDir'];
experimental: Required<Omit<Required<ModuleCompileOptions>['experimental'], 'customRenderer'>> & {
customRenderer: (options: { filename: string }) => string | undefined;
};
};
export type ValidatedCompileOptions = ValidatedModuleCompileOptions &

@ -45,7 +45,15 @@ const common_options = {
experimental: object({
async: boolean(false),
customRenderer: string(undefined)
customRenderer: parametric(
/** @type {(options: { filename: string }) => string | undefined} */ (() => undefined),
(input, keypath) => {
if (input != null && typeof input !== 'string') {
throw_error(`${keypath} should be a string, if specified`);
}
return /** @type {string | undefined} */ (input);
}
)
})
};

@ -1198,7 +1198,7 @@ declare module 'svelte/compiler' {
/**
* Path to a module that exports the custom renderer to use. When this is truthy templating mode will also be automatically set to `functional`
*/
customRenderer?: string;
customRenderer?: string | ((options: { filename: string }) => string | undefined);
};
}
/**
@ -3273,7 +3273,7 @@ declare module 'svelte/types/compiler/interfaces' {
/**
* Path to a module that exports the custom renderer to use. When this is truthy templating mode will also be automatically set to `functional`
*/
customRenderer?: string;
customRenderer?: string | ((options: { filename: string }) => string | undefined);
};
}
/**

Loading…
Cancel
Save