module context

pull/1864/head
Rich Harris 7 years ago
parent 366bf0706a
commit bcaf5e0cb3

@ -32,6 +32,17 @@ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
childKeys.Attribute = ['value']; childKeys.Attribute = ['value'];
childKeys.ExportNamedDeclaration = ['declaration', 'specifiers']; childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
function get_context(script) {
const context = script.attributes.find(attribute => attribute.name === 'context');
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
throw new Error(`context attribute must be static`);
}
return context.value[0].data;
}
export default class Component { export default class Component {
stats: Stats; stats: Stats;
@ -52,12 +63,14 @@ export default class Component {
imports: Node[] = []; imports: Node[] = [];
namespace: string; namespace: string;
hasComponents: boolean; hasComponents: boolean;
module_javascript: string;
javascript: string; javascript: string;
declarations: string[] = []; declarations: string[] = [];
writable_declarations: Set<string> = new Set(); writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set(); initialised_declarations: Set<string> = new Set();
exports: Array<{ name: string, as: string }> = []; exports: Array<{ name: string, as: string }> = [];
module_exports: Array<{ name: string, as: string }> = [];
partly_hoisted: string[] = []; partly_hoisted: string[] = [];
fully_hoisted: string[] = []; fully_hoisted: string[] = [];
@ -109,7 +122,8 @@ export default class Component {
this.properties = new Map(); this.properties = new Map();
this.walkJs(); this.walk_module_js();
this.walk_instance_js();
this.name = this.alias(name); this.name = this.alias(name);
this.meta = process_meta(this, this.ast.html.children); this.meta = process_meta(this, this.ast.html.children);
@ -197,7 +211,18 @@ export default class Component {
? options.shared ? options.shared
: 'svelte/internal.js'; : 'svelte/internal.js';
const module = wrapModule(result, format, name, options, banner, sharedPath, importedHelpers, this.imports, this.source); const module = wrapModule(
result,
format,
name,
options,
banner,
sharedPath,
importedHelpers,
this.imports,
this.module_exports,
this.source
);
const parts = module.split('✂]'); const parts = module.split('✂]');
const finalChunk = parts.pop(); const finalChunk = parts.pop();
@ -351,38 +376,9 @@ export default class Component {
return null; return null;
} }
walkJs() { extract_imports_and_exports(content, imports, exports) {
const { js } = this.ast; const { code } = this;
if (!js) return; const body = content.body.slice(); // TODO do we need to mutate the original?
this.addSourcemapLocations(js.content);
const { code, source, imports } = this;
const indent = code.getIndentString();
code.indent(indent, {
exclude: [
[0, js.content.start],
[js.content.end, source.length]
]
});
let { scope, map, globals } = createScopes(js.content);
this.scope = scope;
scope.declarations.forEach(name => {
this.userVars.add(name);
this.declarations.push(name);
});
this.writable_declarations = scope.writable_declarations;
this.initialised_declarations = scope.initialised_declarations;
globals.forEach(name => {
this.userVars.add(name);
});
const body = js.content.body.slice(); // slice, because we're going to be mutating the original
body.forEach(node => { body.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
@ -397,18 +393,18 @@ export default class Component {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => { node.declaration.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => { extractNames(declarator.id).forEach(name => {
this.exports.push({ name, as: name }); exports.push({ name, as: name });
}); });
}); });
} else { } else {
const { name } = node.declaration.id; const { name } = node.declaration.id;
this.exports.push({ name, as: name }); exports.push({ name, as: name });
} }
code.remove(node.start, node.declaration.start); code.remove(node.start, node.declaration.start);
} else { } else {
node.specifiers.forEach(specifier => { node.specifiers.forEach(specifier => {
this.exports.push({ exports.push({
name: specifier.local.name, name: specifier.local.name,
as: specifier.exported.name as: specifier.exported.name
}); });
@ -419,7 +415,7 @@ export default class Component {
// imports need to be hoisted out of the IIFE // imports need to be hoisted out of the IIFE
// TODO hoist other stuff where possible // TODO hoist other stuff where possible
else if (node.type === 'ImportDeclaration') { else if (node.type === 'ImportDeclaration') {
removeNode(code, js.content, node); removeNode(code, content.start, content.end, body, node);
imports.push(node); imports.push(node);
node.specifiers.forEach((specifier: Node) => { node.specifiers.forEach((specifier: Node) => {
@ -428,10 +424,63 @@ export default class Component {
}); });
} }
}); });
}
walk_module_js() {
const script = this.ast.js.find(script => get_context(script) === 'module');
if (!script) return;
this.addSourcemapLocations(script.content);
// TODO unindent
this.extract_imports_and_exports(script.content, this.imports, this.module_exports);
let a = script.content.start;
while (/\s/.test(this.source[a])) a += 1;
let b = script.content.end;
while (/\s/.test(this.source[b - 1])) b -= 1;
this.module_javascript = a !== b ? `[✂${a}-${b}✂]` : null;
}
walk_instance_js() {
const script = this.ast.js.find(script => get_context(script) === 'default');
if (!script) return;
this.addSourcemapLocations(script.content);
const { code, source, imports } = this;
const indent = code.getIndentString();
code.indent(indent, {
exclude: [
[0, script.content.start],
[script.content.end, source.length]
]
});
let { scope, map, globals } = createScopes(script.content);
this.scope = scope;
scope.declarations.forEach(name => {
this.userVars.add(name);
this.declarations.push(name);
});
this.writable_declarations = scope.writable_declarations;
this.initialised_declarations = scope.initialised_declarations;
globals.forEach(name => {
this.userVars.add(name);
});
this.extract_imports_and_exports(script.content, this.imports, this.exports);
const top_scope = scope; const top_scope = scope;
walk(js.content, { walk(script.content, {
enter: (node, parent) => { enter: (node, parent) => {
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
@ -453,10 +502,10 @@ export default class Component {
} }
}); });
let a = js.content.start; let a = script.content.start;
while (/\s/.test(source[a])) a += 1; while (/\s/.test(source[a])) a += 1;
let b = js.content.end; let b = script.content.end;
while (/\s/.test(source[b - 1])) b -= 1; while (/\s/.test(source[b - 1])) b -= 1;
this.javascript = a !== b ? `[✂${a}-${b}✂]` : ''; this.javascript = a !== b ? `[✂${a}-${b}✂]` : '';

@ -264,15 +264,15 @@ export default class Stylesheet {
this.nodesWithCssClass = new Set(); this.nodesWithCssClass = new Set();
this.nodesWithRefCssClass = new Map(); this.nodesWithRefCssClass = new Map();
if (ast.css && ast.css.children.length) { if (ast.css[0] && ast.css[0].children.length) {
this.id = `svelte-${hash(ast.css.content.styles)}`; this.id = `svelte-${hash(ast.css[0].content.styles)}`;
this.hasStyles = true; this.hasStyles = true;
const stack: (Rule | Atrule)[] = []; const stack: (Rule | Atrule)[] = [];
let currentAtrule: Atrule = null; let currentAtrule: Atrule = null;
walk(this.ast.css, { walk(ast.css[0], {
enter: (node: Node) => { enter: (node: Node) => {
if (node.type === 'Atrule') { if (node.type === 'Atrule') {
const last = stack[stack.length - 1]; const last = stack[stack.length - 1];

@ -219,6 +219,8 @@ export default function dom(
}); });
builder.addBlock(deindent` builder.addBlock(deindent`
${component.module_javascript}
${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')}
class ${name} extends ${superclass} { class ${name} extends ${superclass} {

@ -19,9 +19,12 @@ export default function wrapModule(
sharedPath: string, sharedPath: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: string[],
source: string source: string
): string { ): string {
if (format === 'es') return es(code, name, options, banner, sharedPath, helpers, imports, source); if (format === 'es') {
return es(code, name, options, banner, sharedPath, helpers, imports, module_exports, source);
}
const dependencies = imports.map((declaration, i) => { const dependencies = imports.map((declaration, i) => {
const defaultImport = declaration.specifiers.find( const defaultImport = declaration.specifiers.find(
@ -77,6 +80,7 @@ function es(
sharedPath: string, sharedPath: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: string[],
source: string source: string
) { ) {
const importHelpers = helpers.length > 0 && ( const importHelpers = helpers.length > 0 && (
@ -95,7 +99,8 @@ function es(
${importBlock} ${importBlock}
${code} ${code}
export default ${name};`; export default ${name};
${module_exports.length > 0 && `export { ${module_exports.join(', ')} };`}`;
} }
function amd( function amd(

@ -19,13 +19,13 @@ export class Parser {
readonly filename?: string; readonly filename?: string;
readonly customElement: CustomElementOptions | true; readonly customElement: CustomElementOptions | true;
index: number; index = 0;
stack: Array<Node>; stack: Array<Node> = [];
html: Node; html: Node;
css: Node; css: Node[] = [];
js: Node; js: Node[] = [];
metaTags: {}; metaTags = {};
allowBindings: boolean; allowBindings: boolean;
@ -40,10 +40,6 @@ export class Parser {
this.allowBindings = options.bind !== false; this.allowBindings = options.bind !== false;
this.index = 0;
this.stack = [];
this.metaTags = {};
this.html = { this.html = {
start: null, start: null,
end: null, end: null,
@ -51,9 +47,6 @@ export class Parser {
children: [], children: [],
}; };
this.css = null;
this.js = null;
this.stack.push(this.html); this.stack.push(this.html);
let state: ParserState = fragment; let state: ParserState = fragment;

@ -231,16 +231,8 @@ export default function tag(parser: Parser) {
if (specials.has(name) && parser.stack.length === 1) { if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name); const special = specials.get(name);
if (parser[special.property]) {
parser.index = start;
parser.error({
code: `duplicate-${name}`,
message: `You can only have one top-level <${name}> tag per component`
});
}
parser.eat('>', true); parser.eat('>', true);
parser[special.property] = special.read(parser, start, element.attributes); parser[special.property].push(special.read(parser, start, element.attributes));
return; return;
} }

@ -1,46 +1,37 @@
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { Node } from '../interfaces'; import { Node } from '../interfaces';
const keys = { export function removeNode(
ObjectExpression: 'properties', code: MagicString,
Program: 'body', start: number,
}; end: number,
body: Node,
const offsets = { node: Node
ObjectExpression: [1, -1], ) {
Program: [0, 0], const i = body.indexOf(node);
};
export function removeNode(code: MagicString, parent: Node, node: Node) {
const key = keys[parent.type];
const offset = offsets[parent.type];
if (!key || !offset) throw new Error(`not implemented: ${parent.type}`);
const list = parent[key];
const i = list.indexOf(node);
if (i === -1) throw new Error('node not in list'); if (i === -1) throw new Error('node not in list');
let a; let a;
let b; let b;
if (list.length === 1) { if (body.length === 1) {
// remove everything, leave {} // remove everything, leave {}
a = parent.start + offset[0]; a = start;
b = parent.end + offset[1]; b = end;
} else if (i === 0) { } else if (i === 0) {
// remove everything before second node, including comments // remove everything before second node, including comments
a = parent.start + offset[0]; a = start;
while (/\s/.test(code.original[a])) a += 1; while (/\s/.test(code.original[a])) a += 1;
b = list[i].end; b = body[i].end;
while (/[\s,]/.test(code.original[b])) b += 1; while (/[\s,]/.test(code.original[b])) b += 1;
} else { } else {
// remove the end of the previous node to the end of this one // remove the end of the previous node to the end of this one
a = list[i - 1].end; a = body[i - 1].end;
b = node.end; b = node.end;
} }
code.remove(a, b); code.remove(a, b);
list.splice(i, 1); body.splice(i, 1);
return; return;
} }
Loading…
Cancel
Save