Convert src/compiler/compile/render_dom/index.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent 92aef8e122
commit e8fd1498d6

@ -1,220 +1,171 @@
import type { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping';
import { b, x, p } from 'code-red'; import { b, x, p } from 'code-red';
import Component from '../Component'; import Renderer from './Renderer.js';
import Renderer from './Renderer';
import { CompileOptions, CssResult } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic'; import { extract_names } from 'periscopic';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate.js';
import Block from './Block'; import { apply_preprocessor_sourcemap } from '../../utils/mapped_code.js';
import { import { flatten } from '../../utils/flatten.js';
ImportDeclaration, import check_enable_sourcemap from '../utils/check_enable_sourcemap.js';
ClassDeclaration, import { push_array } from '../../utils/push_array.js';
Node,
Statement, /**
ObjectExpression, * @param {import('../Component.js').default} component
Expression * @param {import('../../interfaces.js').CompileOptions} options
} from 'estree'; * @returns {{ js: import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").import('estree').Node[]; css: import('../../interfaces.js').CssResult; }}
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code'; */
import { flatten } from '../../utils/flatten'; export default function dom(component, options) {
import check_enable_sourcemap from '../utils/check_enable_sourcemap';
import { push_array } from '../../utils/push_array';
export default function dom(
component: Component,
options: CompileOptions
): { js: Node[]; css: CssResult } {
const { name } = component; const { name } = component;
const renderer = new Renderer(component, options); const renderer = new Renderer(component, options);
const { block } = renderer; const { block } = renderer;
block.has_outro_method = true; block.has_outro_method = true;
const body = []; const body = [];
if (renderer.file_var) { if (renderer.file_var) {
const file = component.file ? x`"${component.file}"` : x`undefined`; const file = component.file ? x `"${component.file}"` : x `undefined`;
body.push(b`const ${renderer.file_var} = ${file};`); body.push(b `const ${renderer.file_var} = ${file};`);
} }
const css = component.stylesheet.render(options.filename); const css = component.stylesheet.render(options.filename);
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
if (css_sourcemap_enabled) { if (css_sourcemap_enabled) {
css.map = apply_preprocessor_sourcemap( css.map = apply_preprocessor_sourcemap(options.filename, css.map,
options.filename, /** @type {string | import('@ampproject/remapping').RawSourceMap | import('@ampproject/remapping').DecodedSourceMap} */ (options.sourcemap));
css.map, }
options.sourcemap as string | RawSourceMap | DecodedSourceMap else {
);
} else {
css.map = null; css.map = null;
} }
const styles = css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
const styles =
css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code; : css.code;
const add_css = component.get_unique_name('add_css'); const add_css = component.get_unique_name('add_css');
const should_add_css = !!styles && (options.customElement || options.css === 'injected'); const should_add_css = !!styles && (options.customElement || options.css === 'injected');
if (should_add_css) { if (should_add_css) {
body.push(b` body.push(b `
function ${add_css}(target) { function ${add_css}(target) {
@append_styles(target, "${component.stylesheet.id}", "${styles}"); @append_styles(target, "${component.stylesheet.id}", "${styles}");
} }
`); `);
} }
// fix order // fix order
// TODO the deconflicted names of blocks are reversed... should set them here // TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse(); const blocks = renderer.blocks.slice().reverse();
push_array(body, blocks.map((block) => {
push_array(
body,
blocks.map((block) => {
// TODO this is a horrible mess — renderer.blocks // TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes // contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render(); if (( /** @type {import('./Block.js').default} */(block)).render)
return ( /** @type {import('./Block.js').default} */(block)).render();
return block; return block;
}) }));
);
if (options.dev && !options.hydratable) { if (options.dev && !options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b `throw new @_Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`);
b`throw new @_Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`
);
} }
const uses_slots = component.var_lookup.has('$$slots'); const uses_slots = component.var_lookup.has('$$slots');
let compute_slots: Node[] | undefined;
/** @type {import('estree').Node[] | undefined} */
let compute_slots;
if (uses_slots) { if (uses_slots) {
compute_slots = b` compute_slots = b `
const $$slots = @compute_slots(#slots); const $$slots = @compute_slots(#slots);
`; `;
} }
const uses_props = component.var_lookup.has('$$props'); const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? '$$new_props' : '$$props'; const $$props = uses_props || uses_rest ? '$$new_props' : '$$props';
const props = component.vars.filter((variable) => !variable.module && variable.export_name); const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter((variable) => variable.writable); const writable_props = props.filter((variable) => variable.writable);
const omit_props_names = component.get_unique_name('omit_props_names'); const omit_props_names = component.get_unique_name('omit_props_names');
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`; const compute_rest = x `@compute_rest_props($$props, ${omit_props_names.name})`;
const rest = uses_rest const rest = uses_rest
? b` ? b `
const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}]; const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}];
let $$restProps = ${compute_rest}; let $$restProps = ${compute_rest};
` `
: null; : null;
const set = uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
const set = ? x `
uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
? x`
${$$props} => { ${$$props} => {
${ ${uses_props &&
uses_props && renderer.invalidate('$$props', x `$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)}
renderer.invalidate( ${uses_rest &&
'$$props',
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
)
}
${
uses_rest &&
!uses_props && !uses_props &&
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))` x `$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`}
} ${uses_rest && renderer.invalidate('$$restProps', x `$$restProps = ${compute_rest}`)}
${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)} ${writable_props.map((prop) => b `if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x `${prop.name} = ${$$props}.${prop.export_name}`)};`)}
${writable_props.map( ${component.slots.size > 0 &&
(prop) => b `if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x `$$scope = ${$$props}.$$scope`)};`}
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(
prop.name,
x`${prop.name} = ${$$props}.${prop.export_name}`
)};`
)}
${
component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate(
'$$scope',
x`$$scope = ${$$props}.$$scope`
)};`
}
} }
` `
: null; : null;
const accessors = []; const accessors = [];
const not_equal = component.component_options.immutable ? x `@not_equal` : x `@safe_not_equal`;
const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; /** @type {import('estree').Node[] | import('estree').Node} */
let missing_props_check: Node[] | Node; let missing_props_check;
let inject_state: Expression;
let capture_state: Expression;
let props_inject: Node[] | Node;
/** @type {import('estree').Expression} */
let inject_state;
/** @type {import('estree').Expression} */
let capture_state;
/** @type {import('estree').Node[] | import('estree').Node} */
let props_inject;
props.forEach((prop) => { props.forEach((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) { if (!variable.writable || component.component_options.accessors) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { value: x `function() {
return ${ return ${prop.hoistable
prop.hoistable
? prop.name ? prop.name
: x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]` : x `this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`}
}
}` }`
}); });
} else if (component.compile_options.dev) { }
else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { value: x `function() {
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}` }`
}); });
} }
if (component.component_options.accessors) { if (component.component_options.accessors) {
if (variable.writable && !renderer.readonly.has(prop.name)) { if (variable.writable && !renderer.readonly.has(prop.name)) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'set', kind: 'set',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function(${prop.name}) { value: x `function(${prop.name}) {
this.$$set({ ${prop.export_name}: ${prop.name} }); this.$$set({ ${prop.export_name}: ${prop.name} });
@flush(); @flush();
}` }`
}); });
} else if (component.compile_options.dev) { }
else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'set', kind: 'set',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) { value: x `function(value) {
throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'"); throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'");
}` }`
}); });
} }
} else if (component.compile_options.dev) { }
else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'set', kind: 'set',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) { value: x `function(value) {
throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}` }`
}); });
} }
}); });
component.instance_exports_from.forEach((exports_from) => { component.instance_exports_from.forEach((exports_from) => {
const import_declaration = { const import_declaration = {
...exports_from, ...exports_from,
@ -222,8 +173,7 @@ export default function dom(
specifiers: [], specifiers: [],
source: exports_from.source source: exports_from.source
}; };
component.imports.push(import_declaration as ImportDeclaration); component.imports.push(/** @type {import('estree').ImportDeclaration} */ (import_declaration));
exports_from.specifiers.forEach((specifier) => { exports_from.specifiers.forEach((specifier) => {
if (component.component_options.accessors) { if (component.component_options.accessors) {
const name = component.get_unique_name(specifier.exported.name); const name = component.get_unique_name(specifier.exported.name);
@ -233,189 +183,147 @@ export default function dom(
imported: specifier.local, imported: specifier.local,
local: name local: name
}); });
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: specifier.exported.name }, key: { type: 'Identifier', name: specifier.exported.name },
value: x`function() { value: x `function() {
return ${name} return ${name}
}` }`
}); });
} else if (component.compile_options.dev) { }
else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: 'MethodDefinition',
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: specifier.exported.name }, key: { type: 'Identifier', name: specifier.exported.name },
value: x`function() { value: x `function() {
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}` }`
}); });
} }
}); });
}); });
if (component.compile_options.dev) { if (component.compile_options.dev) {
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter((prop) => prop.writable && !prop.initialised); const expected = props.filter((prop) => prop.writable && !prop.initialised);
if (expected.length) { if (expected.length) {
missing_props_check = b` missing_props_check = b `
$$self.$$.on_mount.push(function () { $$self.$$.on_mount.push(function () {
${expected.map( ${expected.map((prop) => b `
(prop) => b`
if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) { if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}` }`)}
)}
}); });
`; `;
} }
const capturable_vars = component.vars.filter((v) => !v.internal && !v.global && !v.name.startsWith('$$'));
const capturable_vars = component.vars.filter(
(v) => !v.internal && !v.global && !v.name.startsWith('$$')
);
if (capturable_vars.length > 0) { if (capturable_vars.length > 0) {
capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`; capture_state = x `() => ({ ${capturable_vars.map((prop) => p `${prop.name}`)} })`;
} }
const injectable_vars = capturable_vars.filter((v) => !v.module && v.writable && v.name[0] !== '$');
const injectable_vars = capturable_vars.filter(
(v) => !v.module && v.writable && v.name[0] !== '$'
);
if (uses_props || injectable_vars.length > 0) { if (uses_props || injectable_vars.length > 0) {
inject_state = x` inject_state = x `
${$$props} => { ${$$props} => {
${ ${uses_props &&
uses_props && renderer.invalidate('$$props', x `$$props = @assign(@assign({}, $$props), $$new_props)`)}
renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`) ${injectable_vars.map((v) => b `if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x `${v.name} = ${$$props}.${v.name}`)};`)}
}
${injectable_vars.map(
(v) =>
b`if ('${v.name}' in $$props) ${renderer.invalidate(
v.name,
x`${v.name} = ${$$props}.${v.name}`
)};`
)}
} }
`; `;
props_inject = b `
props_inject = b`
if ($$props && "$$inject" in $$props) { if ($$props && "$$inject" in $$props) {
$$self.$inject_state($$props.$$inject); $$self.$inject_state($$props.$$inject);
} }
`; `;
} }
} }
// instrument assignments // instrument assignments
if (component.ast.instance) { if (component.ast.instance) {
let scope = component.instance_scope; let scope = component.instance_scope;
const map = component.instance_scope_map; const map = component.instance_scope_map;
let execution_context: Node | null = null;
/** @type {import('estree').Node | null} */
let execution_context = null;
walk(component.ast.instance.content, { walk(component.ast.instance.content, {
enter(node: Node) { enter(node) {
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node) as Scope; scope = /** @type {import('periscopic').Scope} */ (map.get(node));
if (!execution_context && !scope.block) { if (!execution_context && !scope.block) {
execution_context = node; execution_context = node;
} }
} else if ( }
!execution_context && else if (!execution_context &&
node.type === 'LabeledStatement' && node.type === 'LabeledStatement' &&
node.label.name === '$' node.label.name === '$') {
) {
execution_context = node; execution_context = node;
} }
}, },
leave(node) {
leave(node: Node) {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
} }
if (execution_context === node) { if (execution_context === node) {
execution_context = null; execution_context = null;
} }
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
// normally (`a = 1`, `b.c = 2`), there'll be a single name // normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there // (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones // may be more, in which case we need to tack the extra ones
// onto the initial function call // onto the initial function call
const names = new Set(extract_names(assignee as Node)); const names = new Set(extract_names(/** @type {import('estree').Node} */ (assignee)));
this.replace(invalidate(renderer, scope, node, names, execution_context === null)); this.replace(invalidate(renderer, scope, node, names, execution_context === null));
} }
} }
}); });
component.rewrite_props(({ name, reassigned, export_name }) => { component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`; const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index; const i = renderer.context_lookup.get(`$${name}`).index;
const insert = reassigned || export_name
const insert = ? b `${`$$subscribe_${name}`}()`
reassigned || export_name : b `@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) { if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`; return b `@validate_store(${name}, '${name}'); ${insert}`;
} }
return insert; return insert;
}); });
} }
const args = [x `$$self`];
const args = [x`$$self`]; const has_invalidate = props.length > 0 ||
const has_invalidate =
props.length > 0 ||
component.has_reactive_assignments || component.has_reactive_assignments ||
component.slots.size > 0 || component.slots.size > 0 ||
capture_state || capture_state ||
inject_state; inject_state;
if (has_invalidate) { if (has_invalidate) {
args.push(x`$$props`, x`$$invalidate`); args.push(x `$$props`, x `$$invalidate`);
} else if (component.compile_options.dev) { }
else if (component.compile_options.dev) {
// $$props arg is still needed for unknown prop check // $$props arg is still needed for unknown prop check
args.push(x`$$props`); args.push(x `$$props`);
} }
// has_create_fragment is intentionally to be true in dev mode. // has_create_fragment is intentionally to be true in dev mode.
const has_create_fragment = component.compile_options.dev || block.has_content(); const has_create_fragment = component.compile_options.dev || block.has_content();
if (has_create_fragment) { if (has_create_fragment) {
body.push(b` body.push(b `
function create_fragment(#ctx) { function create_fragment(#ctx) {
${block.get_contents()} ${block.get_contents()}
} }
`); `);
} }
body.push(b `
body.push(b`
${component.extract_javascript(component.ast.module)} ${component.extract_javascript(component.ast.module)}
${component.fully_hoisted} ${component.fully_hoisted}
`); `);
const filtered_props = props.filter((prop) => { const filtered_props = props.filter((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (variable.hoistable)
if (variable.hoistable) return false; return false;
return prop.name[0] !== '$'; return prop.name[0] !== '$';
}); });
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_stores = component.vars.filter(
(variable) => variable.name[0] === '$' && variable.name[1] !== '$'
);
const instance_javascript = component.extract_javascript(component.ast.instance); const instance_javascript = component.extract_javascript(component.ast.instance);
const has_definition = component.compile_options.dev ||
const has_definition =
component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) || (instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 || filtered_props.length > 0 ||
uses_props || uses_props ||
@ -424,106 +332,86 @@ export default function dom(
component.reactive_declarations.length > 0 || component.reactive_declarations.length > 0 ||
capture_state || capture_state ||
inject_state; inject_state;
const definition = has_definition const definition = has_definition
? component.alias('instance') ? component.alias('instance')
: { type: 'Literal', value: null }; : { type: 'Literal', value: null };
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter((store) => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable; return !variable || variable.hoistable;
}) })
.map( .map(({ name }) => b `
({ name }) => b` ${component.compile_options.dev && b `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value));
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${ `);
renderer.context_lookup.get(name).index
}, ${name} = $$value));
`
);
const resubscribable_reactive_store_unsubscribers = reactive_stores const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter((store) => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name); return variable && (variable.reassigned || variable.export_name);
}) })
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); .map(({ name }) => b `$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) { if (has_definition) {
const reactive_declarations: Node | Node[] = [];
const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever
/** @type {import('estree').Node | import('estree').Node[]} */
const reactive_declarations = [];
/** @type {import('estree').Node[]} */
const fixed_reactive_declarations = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach((d) => { component.reactive_declarations.forEach((d) => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps'); const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps');
const writable = dependencies.filter((n) => { const writable = dependencies.filter((n) => {
const variable = component.var_lookup.get(n); const variable = component.var_lookup.get(n);
return variable && (variable.export_name || variable.mutated || variable.reassigned); return variable && (variable.export_name || variable.mutated || variable.reassigned);
}); });
const condition = !uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true);
const condition =
!uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true);
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
if (condition)
if (condition) statement = b`if (${condition}) { ${statement} }`[0] as Statement; statement = /** @type {import('estree').Statement} */ (b `if (${condition}) { ${statement} }`[0]);
if (condition || uses_rest_or_props) { if (condition || uses_rest_or_props) {
reactive_declarations.push(statement); reactive_declarations.push(statement);
} else { }
else {
fixed_reactive_declarations.push(statement); fixed_reactive_declarations.push(statement);
} }
}); });
const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => { const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$'; return variable.injected && variable.name[0] !== '$';
}); });
const reactive_store_declarations = reactive_stores.map((variable) => { const reactive_store_declarations = reactive_stores.map((variable) => {
const $name = variable.name; const $name = variable.name;
const name = $name.slice(1); const name = $name.slice(1);
const store = component.var_lookup.get(name); const store = component.var_lookup.get(name);
if (store && (store.reassigned || store.export_name)) { if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`; const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`; const subscribe = `$$subscribe_${name}`;
const i = renderer.context_lookup.get($name).index; const i = renderer.context_lookup.get($name).index;
return b `let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
} }
return b `let ${$name};`;
return b`let ${$name};`;
}); });
let unknown_props_check: Node[] | undefined; /** @type {import('estree').Node[] | undefined} */
let unknown_props_check;
if (component.compile_options.dev && !(uses_props || uses_rest)) { if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b` unknown_props_check = b `
const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map((prop) => x `'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${ if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
component.tag
}> was created with unknown prop '\${key}'\`);
}); });
`; `;
} }
const return_value = { const return_value = {
type: 'ArrayExpression', type: 'ArrayExpression',
elements: renderer.initial_context.map( elements: renderer.initial_context.map((member) => ( /** @type {import('estree').Expression} */({
(member) =>
({
type: 'Identifier', type: 'Identifier',
name: member.name name: member.name
} as Expression) })))
)
}; };
body.push(b `
body.push(b`
function ${definition}(${args}) { function ${definition}(${args}) {
${injected.map((name) => b`let ${name};`)} ${injected.map((name) => b `let ${name};`)}
${rest} ${rest}
@ -533,17 +421,13 @@ export default function dom(
${resubscribable_reactive_store_unsubscribers} ${resubscribable_reactive_store_unsubscribers}
${ ${component.slots.size || component.compile_options.dev || uses_slots
component.slots.size || component.compile_options.dev || uses_slots ? b `let { $$slots: #slots = {}, $$scope } = $$props;`
? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null}
: null ${component.compile_options.dev &&
} b `@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()]
${
component.compile_options.dev &&
b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()]
.map((key) => `'${key}'`) .map((key) => `'${key}'`)
.join(',')}]);` .join(',')}]);`}
}
${compute_slots} ${compute_slots}
${instance_javascript} ${instance_javascript}
@ -551,86 +435,72 @@ export default function dom(
${missing_props_check} ${missing_props_check}
${unknown_props_check} ${unknown_props_check}
${ ${renderer.binding_groups.size > 0 &&
renderer.binding_groups.size > 0 && b `const $$binding_groups = [${[...renderer.binding_groups.keys()].map((_) => x `[]`)}];`}
b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map((_) => x`[]`)}];`
}
${component.partly_hoisted} ${component.partly_hoisted}
${set && b`$$self.$$set = ${set};`} ${set && b `$$self.$$set = ${set};`}
${capture_state && b`$$self.$capture_state = ${capture_state};`} ${capture_state && b `$$self.$capture_state = ${capture_state};`}
${inject_state && b`$$self.$inject_state = ${inject_state};`} ${inject_state && b `$$self.$inject_state = ${inject_state};`}
${/* before reactive declarations */ props_inject} ${ /* before reactive declarations */props_inject}
${ ${reactive_declarations.length > 0 &&
reactive_declarations.length > 0 && b `
b`
$$self.$$.update = () => { $$self.$$.update = () => {
${reactive_declarations} ${reactive_declarations}
}; };
` `}
}
${fixed_reactive_declarations} ${fixed_reactive_declarations}
${uses_props && b`$$props = @exclude_internal_props($$props);`} ${uses_props && b `$$props = @exclude_internal_props($$props);`}
return ${return_value}; return ${return_value};
} }
`); `);
} }
const prop_indexes = /** @type {import('estree').ObjectExpression} */ (x `{
const prop_indexes = x`{
${props ${props
.filter((v) => v.export_name && !v.module) .filter((v) => v.export_name && !v.module)
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)} .map((v) => p `${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression; }`);
let dirty; let dirty;
if (renderer.context_overflow) { if (renderer.context_overflow) {
dirty = x`[]`; dirty = x `[]`;
for (let i = 0; i < renderer.context.length; i += 31) { for (let i = 0; i < renderer.context.length; i += 31) {
dirty.elements.push(x`-1`); dirty.elements.push(x `-1`);
} }
} }
const superclass = { const superclass = {
type: 'Identifier', type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent' name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
}; };
const optional_parameters = []; const optional_parameters = [];
if (should_add_css) { if (should_add_css) {
optional_parameters.push(add_css); optional_parameters.push(add_css);
} else if (dirty) { }
optional_parameters.push(x`null`); else if (dirty) {
optional_parameters.push(x `null`);
} }
if (dirty) { if (dirty) {
optional_parameters.push(dirty); optional_parameters.push(dirty);
} }
const declaration = /** @type {import('estree').ClassDeclaration} */ (b `
const declaration = b`
class ${name} extends ${superclass} { class ${name} extends ${superclass} {
constructor(options) { constructor(options) {
super(${options.dev && 'options'}); super(${options.dev && 'options'});
@init(this, options, ${definition}, ${ @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
has_create_fragment ? 'create_fragment' : 'null' ${options.dev &&
}, ${not_equal}, ${prop_indexes}, ${optional_parameters}); b `@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${
options.dev &&
b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
}
} }
} }
`[0] as ClassDeclaration; `[0]);
push_array(declaration.body.body, accessors); push_array(declaration.body.body, accessors);
body.push(declaration); body.push(declaration);
if (options.customElement) { if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => { const props_str = writable_props.reduce((def, prop) => {
def[prop.export_name] = def[prop.export_name] =
@ -645,25 +515,17 @@ export default function dom(
.filter((accessor) => !writable_props.some((prop) => prop.export_name === accessor.key.name)) .filter((accessor) => !writable_props.some((prop) => prop.export_name === accessor.key.name))
.map((accessor) => `"${accessor.key.name}"`) .map((accessor) => `"${accessor.key.name}"`)
.join(','); .join(',');
const use_shadow_dom = const use_shadow_dom = component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
if (component.component_options.customElement?.tag) { if (component.component_options.customElement?.tag) {
body.push( body.push(b `@_customElements.define("${component.component_options.customElement.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`);
b`@_customElements.define("${ }
component.component_options.customElement.tag else {
}", @create_custom_element(${name}, ${JSON.stringify( body.push(b `@create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`);
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`
);
} else {
body.push(
b`@create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`
);
} }
} }
return { js: flatten(body), css }; return { js: flatten(body), css };
} }

Loading…
Cancel
Save