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.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 {
stats: Stats;
@ -52,12 +63,14 @@ export default class Component {
imports: Node[] = [];
namespace: string;
hasComponents: boolean;
module_javascript: string;
javascript: string;
declarations: string[] = [];
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set();
exports: Array<{ name: string, as: string }> = [];
module_exports: Array<{ name: string, as: string }> = [];
partly_hoisted: string[] = [];
fully_hoisted: string[] = [];
@ -109,7 +122,8 @@ export default class Component {
this.properties = new Map();
this.walkJs();
this.walk_module_js();
this.walk_instance_js();
this.name = this.alias(name);
this.meta = process_meta(this, this.ast.html.children);
@ -197,7 +211,18 @@ export default class Component {
? options.shared
: '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 finalChunk = parts.pop();
@ -351,38 +376,9 @@ export default class Component {
return null;
}
walkJs() {
const { js } = this.ast;
if (!js) return;
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
extract_imports_and_exports(content, imports, exports) {
const { code } = this;
const body = content.body.slice(); // TODO do we need to mutate the original?
body.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') {
@ -397,18 +393,18 @@ export default class Component {
if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => {
this.exports.push({ name, as: name });
exports.push({ name, as: name });
});
});
} else {
const { name } = node.declaration.id;
this.exports.push({ name, as: name });
exports.push({ name, as: name });
}
code.remove(node.start, node.declaration.start);
} else {
node.specifiers.forEach(specifier => {
this.exports.push({
exports.push({
name: specifier.local.name,
as: specifier.exported.name
});
@ -419,7 +415,7 @@ export default class Component {
// imports need to be hoisted out of the IIFE
// TODO hoist other stuff where possible
else if (node.type === 'ImportDeclaration') {
removeNode(code, js.content, node);
removeNode(code, content.start, content.end, body, node);
imports.push(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;
walk(js.content, {
walk(script.content, {
enter: (node, parent) => {
if (map.has(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;
let b = js.content.end;
let b = script.content.end;
while (/\s/.test(source[b - 1])) b -= 1;
this.javascript = a !== b ? `[✂${a}-${b}✂]` : '';

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

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

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

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

@ -231,16 +231,8 @@ export default function tag(parser: Parser) {
if (specials.has(name) && parser.stack.length === 1) {
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[special.property] = special.read(parser, start, element.attributes);
parser[special.property].push(special.read(parser, start, element.attributes));
return;
}

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