chore: make option accept a function

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

@ -30,7 +30,7 @@ export function compile(source, options) {
const { const {
customElement: customElementOptions, customElement: customElementOptions,
customRenderer, customRenderer: custom_renderer,
...parsed_options ...parsed_options
} = parsed.options || {}; } = parsed.options || {};
@ -43,7 +43,7 @@ export function compile(source, options) {
runes: 'runes' in parsed_options ? () => parsed_options.runes : validated.runes, runes: 'runes' in parsed_options ? () => parsed_options.runes : validated.runes,
experimental: { experimental: {
...validated.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 custom_element_from_option = options.customElement({ filename: options.filename });
const css = options.css({ 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 custom_element = options.customElementOptions ?? custom_element_from_option;
const is_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, event_directive_node: null,
uses_event_attributes: false, uses_event_attributes: false,
custom_element, custom_element,
custom_renderer,
inject_styles: css === 'injected' || is_custom_element, inject_styles: css === 'injected' || is_custom_element,
accessors: accessors:
is_custom_element || is_custom_element ||

@ -60,7 +60,7 @@ export function Attribute(node, context) {
} }
// we can't delegate event handlers in a non dom environment // 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 = node.metadata.delegated =
parent?.type === 'RegularElement' && can_delegate_event(node.name.slice(2)); 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 // We mark the subtree as dynamic so parent elements properly include the child init code
if ( if (
(is_customizable_select_element(node) || node.name === 'selectedcontent') && (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 // Mark the element's own fragment as dynamic so it's not treated as static
node.fragment.metadata.dynamic = true; node.fragment.metadata.dynamic = true;

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

@ -36,7 +36,7 @@ function build_locations(nodes) {
*/ */
export function transform_template(state, namespace, flags = 0) { export function transform_template(state, namespace, flags = 0) {
// custom renderers needs a tree to work because there's no template element we can use // 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(); const expression = tree ? state.template.as_tree() : state.template.as_html();

@ -41,7 +41,7 @@ export function RegularElement(node, context) {
const is_html = const is_html =
context.state.metadata.namespace === 'html' && context.state.metadata.namespace === 'html' &&
node.name !== 'svg' && node.name !== 'svg' &&
!context.state.options.experimental.customRenderer; !context.state.analysis.custom_renderer;
const name = is_html ? node.name.toLowerCase() : node.name; const name = is_html ? node.name.toLowerCase() : node.name;
context.state.template.push_element(name, node.start, is_html); 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) // 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 = const is_custom_element = is_custom_element_node(node) && !context.state.analysis.custom_renderer;
is_custom_element_node(node) && !context.state.options.experimental.customRenderer;
// cloneNode is faster, but it does not instantiate the underlying class of the // 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 // 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) { if (name !== 'class' || value) {
context.state.template.set_prop(attribute.name, value === true ? '' : 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); let { value } = build_attribute_value(attribute.value, context);
context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value))); context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
} else if (name === 'class') { } else if (name === 'class') {
@ -280,7 +279,7 @@ export function RegularElement(node, context) {
name, name,
value, value,
attributes, attributes,
!!context.state.options.experimental.customRenderer !!context.state.analysis.custom_renderer
); );
(has_state ? context.state.update : context.state.init).push(b.stmt(update)); (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 === ''; const empty_string = value.type === 'Literal' && value.value === '';
if (!empty_string) { 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 // custom renderers need to use the method to invoke the renderer
context.state.template.push_text([ context.state.template.push_text([
{ {
@ -377,10 +376,7 @@ export function RegularElement(node, context) {
); );
} }
} }
} else if ( } else if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
// For <option>, <optgroup>, or <select> elements with rich content, we need to branch based on browser support. // 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. // 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. // 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) { if (has_state && !within_bound_contenteditable) {
context.state.update.push(update); context.state.update.push(update);
} else { } else {
if (context.state.options.experimental.customRenderer) { if (context.state.analysis.custom_renderer) {
// custom renderers need to use the method to invoke the renderer // custom renderers need to use the method to invoke the renderer
context.state.init.push(update); context.state.init.push(update);
} else { } else {

@ -110,10 +110,7 @@ export function RegularElement(node, context) {
const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform); const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
if ( if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
rest.push(b.true); rest.push(b.true);
} }
@ -160,10 +157,7 @@ export function RegularElement(node, context) {
const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform); const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
if ( if (is_customizable_select_element(node) && !context.state.analysis.custom_renderer) {
is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer
) {
rest.push(b.true); rest.push(b.true);
} }
@ -201,7 +195,7 @@ export function RegularElement(node, context) {
if ( if (
(name === 'optgroup' || name === 'select') && (name === 'optgroup' || name === 'select') &&
is_customizable_select_element(node) && is_customizable_select_element(node) &&
!context.state.options.experimental.customRenderer !context.state.analysis.custom_renderer
) { ) {
state.template.push(b.literal('<!>')); 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 * or if not present a boolean which corresponds to the compiler option value
*/ */
custom_element: boolean | AST.SvelteOptions['customElement']; 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 */ /** If `true`, should append styles through JavaScript */
inject_styles: boolean; inject_styles: boolean;
reactive_statements: Map<LabeledStatement, ReactiveStatement>; 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` * 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'> & { export type ValidatedModuleCompileOptions = Omit<Required<ModuleCompileOptions>, 'rootDir'> & {
rootDir: ModuleCompileOptions['rootDir']; rootDir: ModuleCompileOptions['rootDir'];
experimental: Required<Omit<Required<ModuleCompileOptions>['experimental'], 'customRenderer'>> & {
customRenderer: (options: { filename: string }) => string | undefined;
};
}; };
export type ValidatedCompileOptions = ValidatedModuleCompileOptions & export type ValidatedCompileOptions = ValidatedModuleCompileOptions &

@ -45,7 +45,15 @@ const common_options = {
experimental: object({ experimental: object({
async: boolean(false), 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` * 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` * 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