mirror of https://github.com/sveltejs/svelte
remove some unused stuff (#10299)
Co-authored-by: Rich Harris <rich.harris@vercel.com>pull/10348/head
parent
23b38a3471
commit
dc8ca4661f
@ -1,841 +0,0 @@
|
||||
import { parse } from 'acorn';
|
||||
import type { OptimizeOptions } from '#compiler';
|
||||
import { type NodePath, traverse, is, type Scope } from 'estree-toolkit';
|
||||
import type {
|
||||
CallExpression,
|
||||
FunctionDeclaration,
|
||||
ImportDeclaration,
|
||||
VariableDeclaration,
|
||||
Node,
|
||||
Identifier,
|
||||
Function,
|
||||
BlockStatement,
|
||||
Expression
|
||||
} from 'estree';
|
||||
import { print } from 'esrap';
|
||||
import { error } from '../errors.js';
|
||||
|
||||
type Props =
|
||||
| Map<string, { path: null | NodePath<VariableDeclaration>; type: 'static' | 'dynamic' }>
|
||||
| 'unknown';
|
||||
|
||||
interface IfBlock {
|
||||
condition: Node;
|
||||
is_static: boolean;
|
||||
type: 'if';
|
||||
}
|
||||
|
||||
interface AwaitBlock {
|
||||
is_static: boolean;
|
||||
type: 'await';
|
||||
}
|
||||
|
||||
interface EachBlock {
|
||||
collection: Node;
|
||||
is_static: boolean;
|
||||
type: 'each';
|
||||
}
|
||||
|
||||
interface ComponentState {
|
||||
props: Props;
|
||||
needs_template: boolean;
|
||||
is_static: boolean;
|
||||
template: {
|
||||
open: null | NodePath<VariableDeclaration>;
|
||||
traverse: Array<NodePath<VariableDeclaration>>;
|
||||
close: null | NodePath<CallExpression>;
|
||||
components: {
|
||||
path: NodePath<CallExpression>;
|
||||
state: ComponentState;
|
||||
}[];
|
||||
blocks: Map<NodePath<CallExpression>, IfBlock | AwaitBlock | EachBlock>;
|
||||
render_effects: Map<
|
||||
NodePath<CallExpression>,
|
||||
{
|
||||
is_static: boolean;
|
||||
anchors: Set<NodePath<VariableDeclaration>>;
|
||||
expressions: Set<NodePath<VariableDeclaration>>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
references: ComponentState[];
|
||||
}
|
||||
|
||||
interface ModuleContext {
|
||||
parent: null;
|
||||
needs_template: boolean;
|
||||
type: 'module';
|
||||
}
|
||||
|
||||
interface FunctionContext {
|
||||
parent: ModuleContext | ComponentContext | FunctionContext;
|
||||
needs_template: boolean;
|
||||
type: 'function';
|
||||
}
|
||||
|
||||
interface ComponentContext {
|
||||
parent: ModuleContext | ComponentContext;
|
||||
needs_template: boolean;
|
||||
type: 'component';
|
||||
}
|
||||
|
||||
interface OptimizeState {
|
||||
components: Map<NodePath<FunctionDeclaration>, ComponentState>;
|
||||
context: ModuleContext | FunctionContext | ComponentContext;
|
||||
}
|
||||
|
||||
function visit_component(
|
||||
component: NodePath<FunctionDeclaration>,
|
||||
parent_component: null | ComponentState,
|
||||
props: Props,
|
||||
state: OptimizeState
|
||||
): ComponentState {
|
||||
let component_state = state.components.get(component);
|
||||
let initial_visit = true;
|
||||
|
||||
if (component_state === undefined) {
|
||||
component_state = {
|
||||
props,
|
||||
references: [],
|
||||
is_static: true,
|
||||
needs_template: state.context.needs_template,
|
||||
template: {
|
||||
open: null,
|
||||
traverse: [],
|
||||
close: null,
|
||||
components: [],
|
||||
blocks: new Map(),
|
||||
render_effects: new Map()
|
||||
}
|
||||
};
|
||||
if (parent_component !== null) {
|
||||
component_state.references.push(parent_component);
|
||||
}
|
||||
state.components.set(component, component_state);
|
||||
} else {
|
||||
initial_visit = false;
|
||||
if (state.context.needs_template && !component_state.needs_template) {
|
||||
component_state.needs_template = true;
|
||||
component_state.is_static = false;
|
||||
}
|
||||
}
|
||||
|
||||
const template = component_state.template;
|
||||
const visitor = {
|
||||
CallExpression(path: NodePath<CallExpression>) {
|
||||
const callee = path.node!.callee;
|
||||
const callee_path = path.get('callee');
|
||||
|
||||
if (!is.identifier(callee)) {
|
||||
return;
|
||||
}
|
||||
const callee_name = callee.name;
|
||||
const args = path.node!.arguments;
|
||||
const grand_path = path.parentPath?.parentPath;
|
||||
const blocks = component_state!.template.blocks;
|
||||
|
||||
if (initial_visit) {
|
||||
// $.open
|
||||
if (
|
||||
is.variableDeclaration(grand_path) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'open' || callee_name === 'open_frag') &&
|
||||
// TODO: this won't optimize slots, needs some thought
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
template.open = grand_path;
|
||||
}
|
||||
// $.close
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'close' || callee_name === 'close_frag') &&
|
||||
// TODO: this won't optimize slots, needs some thought
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
template.close = path;
|
||||
}
|
||||
// $.child / $.child_frag / $.sibling
|
||||
if (
|
||||
is.variableDeclaration(grand_path) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'child' || callee_name === 'child_frag' || callee_name === 'sibling') &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
template.traverse.push(grand_path);
|
||||
}
|
||||
// $.delegated_event / $.transition / $.in / $.out / $.action / $.event / $.slot / $.auto_focus / $.component / $.element
|
||||
if (
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'delegated_event' ||
|
||||
callee_name === 'transition' ||
|
||||
callee_name === 'in_fn' ||
|
||||
callee_name === 'out' ||
|
||||
callee_name === 'event' ||
|
||||
callee_name === 'action' ||
|
||||
callee_name === 'slot' ||
|
||||
callee_name === 'auto_focus' ||
|
||||
callee_name === 'component' ||
|
||||
callee_name === 'element') &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.bind_value / $.bind_content_editable / $.bind_group / $.bind_property / $.bind_scroll / $.bind_checked / $.bind_online / $.bind_this
|
||||
// TODO: these should go likey be optimized out, but bail-out for now until we have that working
|
||||
if (
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'bind_value' ||
|
||||
callee_name === 'bind_content_editable' ||
|
||||
callee_name === 'bind_group' ||
|
||||
callee_name === 'bind_property' ||
|
||||
callee_name === 'bind_scroll' ||
|
||||
callee_name === 'bind_checked' ||
|
||||
callee_name === 'bind_this' ||
|
||||
callee_name === 'bind_online') &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.source / $.derived / $.prop_source
|
||||
if (
|
||||
is.variableDeclaration(grand_path) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'source' ||
|
||||
callee_name === 'prop_source' ||
|
||||
callee_name === 'derived') &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.effect / $.pre_effect
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
(callee_name === 'effect' || callee_name === 'pre_effect') &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.render_effect
|
||||
// TODO: what about detection of DOM properties that need to be client-side?
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name === 'render_effect' &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
const closure = path.get('arguments')[0];
|
||||
let render_effect = template.render_effects.get(path);
|
||||
if (render_effect === undefined) {
|
||||
render_effect = {
|
||||
is_static: true,
|
||||
anchors: new Set(),
|
||||
expressions: new Set()
|
||||
};
|
||||
template.render_effects.set(path, render_effect);
|
||||
}
|
||||
let is_render_effect_static = true;
|
||||
closure.traverse({
|
||||
Identifier(path: NodePath<Identifier>) {
|
||||
if (is_reactive(path, props, false)) {
|
||||
is_render_effect_static = false;
|
||||
} else if (path.node!.name.includes('_anchor')) {
|
||||
const binding = path.scope!.getBinding(path.node!.name);
|
||||
|
||||
if (
|
||||
binding != null &&
|
||||
is.variableDeclarator(binding.path) &&
|
||||
is.variableDeclaration(binding.path.parentPath)
|
||||
) {
|
||||
render_effect!.anchors.add(binding.path.parentPath);
|
||||
}
|
||||
} else if (path.node!.name.includes('_expression')) {
|
||||
const binding = path.scope!.getBinding(path.node!.name);
|
||||
|
||||
if (
|
||||
binding != null &&
|
||||
is.variableDeclarator(binding.path) &&
|
||||
is.variableDeclaration(binding.path.parentPath)
|
||||
) {
|
||||
render_effect!.expressions.add(binding.path.parentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!is_render_effect_static) {
|
||||
render_effect.is_static = false;
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
path.skipChildren();
|
||||
}
|
||||
// $.prop
|
||||
if (
|
||||
is.variableDeclaration(grand_path) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name === 'prop' &&
|
||||
state.context.type === 'component'
|
||||
) {
|
||||
const prop_key = args[1];
|
||||
if (is.literal(prop_key) && props !== 'unknown') {
|
||||
const prop = props.get(prop_key.value as string);
|
||||
if (prop !== undefined) {
|
||||
prop.path = grand_path;
|
||||
if (prop.type === 'dynamic') {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// $.if
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name.startsWith('if_block')
|
||||
) {
|
||||
let condition = path.get('arguments')[1] as NodePath<Expression>;
|
||||
if (is.arrowFunctionExpression(condition)) {
|
||||
condition = condition.get('body') as NodePath<Expression>;
|
||||
}
|
||||
const is_static = !state.context.needs_template && !is_reactive(condition, props, false);
|
||||
let if_block = blocks.get(path) as undefined | IfBlock;
|
||||
|
||||
if (if_block === undefined) {
|
||||
if_block = {
|
||||
condition: condition.node!,
|
||||
is_static,
|
||||
type: 'if'
|
||||
};
|
||||
blocks.set(path, if_block);
|
||||
} else if (!is_static) {
|
||||
if_block.is_static = false;
|
||||
}
|
||||
path.skipChildren();
|
||||
if (!if_block.is_static) {
|
||||
component_state!.is_static = false;
|
||||
const consequent_fn = path.get('arguments')[0];
|
||||
const alternate_fn = path.get('arguments')[1];
|
||||
const prev_needs_template = state.context.needs_template;
|
||||
state.context.needs_template = true;
|
||||
if (is.function(consequent_fn)) {
|
||||
consequent_fn.traverse(visitor);
|
||||
}
|
||||
if (is.function(alternate_fn)) {
|
||||
alternate_fn.traverse(visitor);
|
||||
}
|
||||
state.context.needs_template = prev_needs_template;
|
||||
}
|
||||
}
|
||||
// $.await
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name === 'await_block'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.key
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name === 'key'
|
||||
) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
// $.each
|
||||
if (
|
||||
is.expressionStatement(path.parentPath) &&
|
||||
is_svelte_import(callee_path) &&
|
||||
callee_name === 'each'
|
||||
) {
|
||||
let collection = path.get('arguments')[1] as NodePath<Expression>;
|
||||
if (is.arrowFunctionExpression(collection)) {
|
||||
collection = collection.get('body') as NodePath<Expression>;
|
||||
}
|
||||
const is_static = !state.context.needs_template && !is_reactive(collection, props, false);
|
||||
let each_block = blocks.get(path) as undefined | EachBlock;
|
||||
|
||||
if (each_block === undefined) {
|
||||
each_block = {
|
||||
collection: collection.node!,
|
||||
is_static,
|
||||
type: 'each'
|
||||
};
|
||||
blocks.set(path, each_block);
|
||||
} else {
|
||||
error(null, 'TODO', '');
|
||||
}
|
||||
path.skipChildren();
|
||||
if (!each_block.is_static) {
|
||||
component_state!.is_static = false;
|
||||
const each_fn = path.get('arguments')[4];
|
||||
const else_fn = path.get('arguments')[5];
|
||||
const prev_needs_template = state.context.needs_template;
|
||||
state.context.needs_template = true;
|
||||
if (is.function(each_fn)) {
|
||||
each_fn.traverse(visitor);
|
||||
}
|
||||
if (is.function(else_fn)) {
|
||||
else_fn.traverse(visitor);
|
||||
}
|
||||
state.context.needs_template = prev_needs_template;
|
||||
}
|
||||
}
|
||||
// <Component />
|
||||
if (
|
||||
callee_name[0] === callee_name[0].toUpperCase() &&
|
||||
is.expressionStatement(path.parentPath)
|
||||
) {
|
||||
const binding = path.scope!.getBinding(callee_name);
|
||||
if (
|
||||
binding != null &&
|
||||
is.functionDeclaration(binding.path) &&
|
||||
binding.path.node!.params.length === 3 &&
|
||||
is.identifier(binding.path.node!.params[1]) &&
|
||||
binding.path.node!.params[1].name === '$$props'
|
||||
) {
|
||||
const context: ComponentContext = {
|
||||
parent: state.context as ComponentContext,
|
||||
needs_template: state.context.needs_template,
|
||||
type: 'component'
|
||||
};
|
||||
state.context = context;
|
||||
const child_props = get_props(path.get('arguments')[1], props);
|
||||
const child_component_state = visit_component(
|
||||
binding.path,
|
||||
component_state!,
|
||||
child_props,
|
||||
state
|
||||
);
|
||||
if (!child_component_state.is_static) {
|
||||
component_state!.is_static = false;
|
||||
}
|
||||
template.components.push({
|
||||
path,
|
||||
state: child_component_state
|
||||
});
|
||||
state.context = state.context.parent;
|
||||
}
|
||||
}
|
||||
},
|
||||
Function: {
|
||||
enter(path: NodePath<Function>) {
|
||||
if (path !== component) {
|
||||
const context: FunctionContext = {
|
||||
needs_template: state.context.needs_template,
|
||||
parent: state.context as ComponentContext | FunctionContext | ModuleContext,
|
||||
type: 'function'
|
||||
};
|
||||
state.context = context;
|
||||
}
|
||||
},
|
||||
leave(path: NodePath<Function>) {
|
||||
if (path !== component) {
|
||||
state.context = state.context.parent as
|
||||
| ComponentContext
|
||||
| FunctionContext
|
||||
| ModuleContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
component.traverse(visitor);
|
||||
return component_state;
|
||||
}
|
||||
|
||||
function get_props(props_arg: NodePath<Node>, parent_props: Props | null): Props {
|
||||
const props: Props = new Map();
|
||||
|
||||
if (is.objectExpression(props_arg)) {
|
||||
for (const property of props_arg.get('properties')) {
|
||||
if (
|
||||
is.property(property) &&
|
||||
(is.identifier(property.node!.key) || is.literal(property.node!.key))
|
||||
) {
|
||||
const value = property.get('value');
|
||||
const kind = property.node!.kind;
|
||||
if (kind === 'init') {
|
||||
const dynamic = is_reactive(value, parent_props, true);
|
||||
props.set(
|
||||
is.literal(property.node!.key)
|
||||
? (property.node!.key.value as string)
|
||||
: property.node!.key.name,
|
||||
{
|
||||
type: dynamic ? 'dynamic' : 'static',
|
||||
path: null
|
||||
}
|
||||
);
|
||||
} else if (
|
||||
kind === 'get' &&
|
||||
is.functionExpression(value) &&
|
||||
value.node!.body.body.length === 1 &&
|
||||
is.returnStatement(value.node!.body.body[0])
|
||||
) {
|
||||
const expression = (value.get('body') as NodePath<BlockStatement>)
|
||||
.get('body')[0]
|
||||
.get('argument') as NodePath<Expression>;
|
||||
const dynamic = is_reactive(expression, parent_props, true);
|
||||
props.set(
|
||||
is.literal(property.node!.key)
|
||||
? (property.node!.key.value as string)
|
||||
: property.node!.key.name,
|
||||
{
|
||||
type: dynamic ? 'dynamic' : 'static',
|
||||
path: null
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
error(null, 'TODO', '');
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
error(null, 'TODO', '');
|
||||
return 'unknown';
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function is_reactive(
|
||||
path: NodePath<Node>,
|
||||
props: Props | null,
|
||||
functions_are_reactive: boolean
|
||||
): boolean {
|
||||
if (is.identifier(path)) {
|
||||
const binding = path.scope!.getBinding(path.node!.name);
|
||||
if (binding != null && is.variableDeclarator(binding.path)) {
|
||||
const init = binding.path.get('init');
|
||||
if (init.node! !== null && is_reactive(init, props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (is.literal(path)) {
|
||||
return false;
|
||||
} else if (is.unaryExpression(path)) {
|
||||
if (is_reactive(path.get('argument'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.updateExpression(path)) {
|
||||
if (is_reactive(path.get('argument'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.assignmentExpression(path)) {
|
||||
if (is_reactive(path.get('left'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
if (is_reactive(path.get('right'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.sequenceExpression(path)) {
|
||||
for (const expression of path.get('expressions')) {
|
||||
if (is_reactive(expression, props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (is.conditionalExpression(path)) {
|
||||
if (is_reactive(path.get('test'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
if (is_reactive(path.get('consequent'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
if (is_reactive(path.get('alternate'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.binaryExpression(path)) {
|
||||
if (is_reactive(path.get('left'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
if (is_reactive(path.get('right'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.logicalExpression(path)) {
|
||||
if (is_reactive(path.get('left'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
if (is_reactive(path.get('right'), props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
} else if (is.arrayExpression(path)) {
|
||||
for (const element of path.get('elements')) {
|
||||
if (element !== null && is_reactive(element, props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (is.objectExpression(path)) {
|
||||
for (const property of path.get('properties')) {
|
||||
if (is_reactive(property, props, functions_are_reactive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (is.function(path)) {
|
||||
return functions_are_reactive;
|
||||
} else if (is.callExpression(path)) {
|
||||
if (is.identifier(path.node!.callee)) {
|
||||
const prop_key = path.node!.arguments[1];
|
||||
const callee_name = path.node!.callee.name;
|
||||
// Check if prop
|
||||
if (
|
||||
is.variableDeclaration(path.parentPath?.parentPath) &&
|
||||
is_svelte_import(path.get('callee')) &&
|
||||
callee_name === 'prop' &&
|
||||
is.literal(prop_key) &&
|
||||
props !== 'unknown' &&
|
||||
props !== null
|
||||
) {
|
||||
const prop = props.get(prop_key.value as string);
|
||||
if (prop !== undefined && prop.type === 'static') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check if referencing prop
|
||||
const binding = path.scope!.getBinding(callee_name);
|
||||
if (binding != null) {
|
||||
if (
|
||||
is.variableDeclarator(binding.path) &&
|
||||
is.variableDeclaration(binding.path.parentPath) &&
|
||||
is.callExpression(binding.path.node!.init) &&
|
||||
is.identifier(binding.path.node!.init.callee) &&
|
||||
binding.path.node!.init.callee.name === 'prop' &&
|
||||
is_svelte_import((binding.path.get('init') as NodePath<CallExpression>).get('callee')) &&
|
||||
is.literal(binding.path.node!.init.arguments[1]) &&
|
||||
props !== 'unknown' &&
|
||||
props !== null
|
||||
) {
|
||||
const prop = props.get(binding.path.node!.init.arguments[1].value as string);
|
||||
if (prop !== undefined && prop.type === 'static') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if template
|
||||
if (
|
||||
is.variableDeclaration(path.parentPath?.parentPath) &&
|
||||
is_svelte_import(path.get('callee')) &&
|
||||
(callee_name === 'child' || callee_name === 'child_frag' || callee_name === 'sibling')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (is.memberExpression(path)) {
|
||||
return true;
|
||||
} else {
|
||||
error(null, 'TODO', '');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function is_svelte_import(path: NodePath<Node>): boolean {
|
||||
if (is.identifier(path)) {
|
||||
const binding = path.scope!.getBinding(path.node!.name);
|
||||
if (binding != null && is.importSpecifier(binding.path)) {
|
||||
const import_declaration = binding.path.parentPath as NodePath<ImportDeclaration>;
|
||||
if ((import_declaration.node!.source.value as string).includes('vendor')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function is_removed(path: NodePath<Node>): boolean {
|
||||
let current_path: null | NodePath<Node> = path;
|
||||
while (current_path !== null) {
|
||||
if (current_path.removed) {
|
||||
return true;
|
||||
}
|
||||
current_path = current_path.parentPath as null | NodePath<Node>;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function has_references(name: string, scope: Scope): boolean {
|
||||
const binding = scope.getBinding(name)!;
|
||||
let has_references = false;
|
||||
for (const reference of binding.references) {
|
||||
if (!is_removed(reference)) {
|
||||
has_references = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return has_references;
|
||||
}
|
||||
|
||||
function optimize_component(component_state: ComponentState): void {
|
||||
const template = component_state.template;
|
||||
const is_static = component_state.is_static;
|
||||
if (template.open !== null) {
|
||||
const arg_to_remove = template.open
|
||||
.get('declarations')[0]
|
||||
.get('init')
|
||||
.get('arguments')[1] as NodePath<Identifier>;
|
||||
const template_path = arg_to_remove.scope!.getBinding(arg_to_remove.node!.name)!.path
|
||||
.parentPath!;
|
||||
template_path.remove();
|
||||
if (is_static) {
|
||||
template.open.remove();
|
||||
} else {
|
||||
arg_to_remove.remove();
|
||||
}
|
||||
}
|
||||
if (is_static && template.close !== null) {
|
||||
template.close.remove();
|
||||
}
|
||||
for (const [path, render_effect] of template.render_effects) {
|
||||
if (render_effect.is_static) {
|
||||
path.remove();
|
||||
for (const anchor of render_effect.anchors) {
|
||||
anchor.remove();
|
||||
}
|
||||
for (const expression of render_effect.expressions) {
|
||||
expression.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [block_path, block] of template.blocks) {
|
||||
if (is_static || block.is_static) {
|
||||
if (block.type === 'each') {
|
||||
const collection = block.collection;
|
||||
if (is.identifier(collection)) {
|
||||
const binding = block_path.scope!.getBinding(collection.name);
|
||||
if (binding != null && binding.references.length === 1) {
|
||||
binding.path.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove the block
|
||||
block_path.remove();
|
||||
}
|
||||
}
|
||||
const reverse_template = template.traverse.slice().reverse();
|
||||
for (const path of reverse_template) {
|
||||
const id = path.node!.declarations[0].id! as Identifier;
|
||||
if (is_static || !has_references(id.name, path.scope!)) {
|
||||
if (!path.removed) {
|
||||
path.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const { path, state } of template.components) {
|
||||
if (state.is_static) {
|
||||
path.parentPath?.getPrevSibling()?.remove();
|
||||
}
|
||||
}
|
||||
if (component_state.props !== 'unknown') {
|
||||
for (const [prop, { path, type }] of component_state.props) {
|
||||
if (type === 'static' && path !== null && !has_references(prop, path.scope!)) {
|
||||
path.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_optimizable(component_state: ComponentState): boolean {
|
||||
if (component_state.needs_template || component_state.props === 'unknown') {
|
||||
return false;
|
||||
}
|
||||
for (const [, { type }] of component_state.props) {
|
||||
if (type === 'dynamic') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function optimize_chunk(source: string, options: OptimizeOptions): string | null {
|
||||
const ast = parse(source, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 13,
|
||||
locations: true
|
||||
});
|
||||
|
||||
const context: ModuleContext = {
|
||||
parent: null,
|
||||
needs_template: !options.hydrate,
|
||||
type: 'module'
|
||||
};
|
||||
const state: OptimizeState = {
|
||||
components: new Map(),
|
||||
context
|
||||
};
|
||||
|
||||
traverse(ast, {
|
||||
$: { scope: true },
|
||||
CallExpression(path: NodePath<CallExpression>) {
|
||||
// TODO: mount signature changed, this needs updating
|
||||
// Find the root component from a `mount(() => component(...), container)` call
|
||||
const node = path.node!;
|
||||
const callee = node.callee;
|
||||
const args = node.arguments;
|
||||
const first_arg = path.get('arguments')[0];
|
||||
|
||||
if (
|
||||
is.identifier(callee) &&
|
||||
callee.name === 'mount' &&
|
||||
args.length === 2 &&
|
||||
is.arrowFunctionExpression(first_arg) &&
|
||||
is.callExpression(first_arg.node!.body)
|
||||
) {
|
||||
if (!is_svelte_import(path.get('callee'))) {
|
||||
return;
|
||||
}
|
||||
const body = first_arg.get('body') as NodePath<CallExpression>;
|
||||
const component_callee = body.node!.callee;
|
||||
const component_args = body.node!.arguments[1];
|
||||
|
||||
if (!is.identifier(component_callee) || component_args == null) {
|
||||
return;
|
||||
}
|
||||
const component_binding = path.scope!.getBinding(component_callee.name);
|
||||
|
||||
if (
|
||||
component_binding == null ||
|
||||
!is.functionDeclaration(component_binding.path) ||
|
||||
component_binding.references.length !== 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const component = component_binding.path;
|
||||
const component_node = component.node!;
|
||||
|
||||
if (
|
||||
component_node.params.length !== 3 ||
|
||||
!is.identifier(component_node.params[1]) ||
|
||||
component_node.params[1].name !== '$$props'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have the root render node
|
||||
const context: ComponentContext = {
|
||||
parent: state.context as ModuleContext,
|
||||
needs_template: state.context.needs_template,
|
||||
type: 'component'
|
||||
};
|
||||
state.context = context;
|
||||
const props = get_props(body.get('arguments')[1], null);
|
||||
visit_component(component, null, props, state);
|
||||
state.context = state.context.parent;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const [, component_state] of state.components) {
|
||||
if (is_optimizable(component_state)) {
|
||||
optimize_component(component_state);
|
||||
}
|
||||
}
|
||||
|
||||
return print(ast as Node).code;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
const regex_tabs = /^\t+/;
|
||||
|
||||
/** @param {string} str */
|
||||
function tabs_to_spaces(str) {
|
||||
return str.replace(regex_tabs, /** @param {any} match */ (match) => match.split('\t').join(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
*/
|
||||
export default function get_code_frame(source, line, column) {
|
||||
const lines = source.split('\n');
|
||||
|
||||
const frame_start = Math.max(0, line - 2);
|
||||
const frame_end = Math.min(line + 3, lines.length);
|
||||
|
||||
const digits = String(frame_end + 1).length;
|
||||
|
||||
return lines
|
||||
.slice(frame_start, frame_end)
|
||||
.map(
|
||||
/**
|
||||
* @param {any} str
|
||||
* @param {any} i
|
||||
*/ (str, i) => {
|
||||
const is_error_line = frame_start + i === line;
|
||||
const line_num = String(i + frame_start + 1).padStart(digits, ' ');
|
||||
|
||||
if (is_error_line) {
|
||||
const indicator =
|
||||
' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
|
||||
return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
|
||||
}
|
||||
|
||||
return `${line_num}: ${tabs_to_spaces(str)}`;
|
||||
}
|
||||
)
|
||||
.join('\n');
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/** @param {import('#compiler').TemplateNode} node */
|
||||
export function to_string(node) {
|
||||
switch (node.type) {
|
||||
case 'IfBlock':
|
||||
return '{#if} block';
|
||||
case 'AwaitBlock':
|
||||
return '{#await} block';
|
||||
case 'EachBlock':
|
||||
return '{#each} block';
|
||||
case 'HtmlTag':
|
||||
return '{@html} block';
|
||||
case 'DebugTag':
|
||||
return '{@debug} block';
|
||||
case 'ConstTag':
|
||||
return '{@const} tag';
|
||||
case 'RegularElement':
|
||||
case 'Component':
|
||||
case 'SlotElement':
|
||||
case 'TitleElement':
|
||||
return `<${node.name}> tag`;
|
||||
default:
|
||||
return node.type;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Pushes all `items` into `array` using `push`, therefore mutating the array.
|
||||
* We do this for memory and perf reasons, and because `array.push(...items)` would
|
||||
* run into a "max call stack size exceeded" error with too many items (~65k).
|
||||
* @param {T[]} array undefined
|
||||
* @param {T[]} items undefined
|
||||
* @template T
|
||||
* @returns {void}
|
||||
*/
|
||||
export function push_array(array, items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
array.push(items[i]);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { regex_starts_with_whitespaces, regex_ends_with_whitespaces } from '../../patterns.js';
|
||||
|
||||
/** @param {string} str */
|
||||
export function trim_start(str) {
|
||||
return str.replace(regex_starts_with_whitespaces, '');
|
||||
}
|
||||
|
||||
/** @param {string} str */
|
||||
export function trim_end(str) {
|
||||
return str.replace(regex_ends_with_whitespaces, '');
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue