From 0bcbe965dc0cb3710374845170f10bdb7d398f80 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Tue, 10 Sep 2019 22:35:58 -0400 Subject: [PATCH] baby steps --- package-lock.json | 8 +- package.json | 2 +- src/compiler/compile/Component.ts | 71 ++----- src/compiler/compile/create_module.ts | 225 ++++++++++++++--------- src/compiler/compile/render_dom/Block.ts | 29 +-- src/compiler/compile/render_dom/index.ts | 22 ++- test/runtime/index.js | 7 +- test/runtime/samples/_/_config.js | 1 + 8 files changed, 196 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f3c60c8d5..7479160cad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -507,13 +507,13 @@ "dev": true }, "code-red": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.6.tgz", - "integrity": "sha512-nsAPEQ+o1Z8UyIbOaOqfTxWHaA4JarPH7OBxDPmqAkkRvATvc2j7khlaAtsXnFreH2fTwekmZed4GlHvY3hQiw==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.7.tgz", + "integrity": "sha512-UgcXT2tiYOFe5MF6FYeP1s+XKvd5XFlm+p+By6/Q/0/NwgT2ZqkR32liLhosDivtO1vvtLTg51LN1LUW5AYSEw==", "dev": true, "requires": { "acorn": "^7.0.0", - "astring": "github:Rich-Harris/astring#generic-handler", + "astring": "github:Rich-Harris/astring#ff83f5e4e75b304cdd428ada4a71372276b0084d", "estree-walker": "^0.6.1", "is-reference": "^1.1.3", "periscopic": "^1.0.0", diff --git a/package.json b/package.json index 945b01cf12..42eb37276f 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "acorn": "^7.0.0", "agadoo": "^1.0.1", "c8": "^5.0.1", - "code-red": "0.0.6", + "code-red": "0.0.7", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index addc00b3eb..c915251e4d 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -1,4 +1,4 @@ -import MagicString, { Bundle } from 'magic-string'; +import MagicString from 'magic-string'; // @ts-ignore import { walk, childKeys } from 'estree-walker'; import { getLocator } from 'locate-character'; @@ -333,8 +333,16 @@ export default class Component { // } // ); - const printed = print({ type: 'Program', body: result } as any); - console.log(printed); + const program: any = { type: 'Program', body: result }; + + walk(program, { + enter: (node) => { + if (node.type === 'Identifier' && node.name[0] === '@') { + const alias = this.helper(node.name.slice(1)); + node.name = alias.name; + } + } + }); const referenced_globals = Array.from( this.globals, @@ -348,10 +356,10 @@ export default class Component { alias, })); - const module = create_module( - printed.code, + create_module( + program, format, - name.name, + name, banner, compile_options.sveltePath, imported_helpers, @@ -362,61 +370,14 @@ export default class Component { .map(variable => ({ name: variable.name, as: variable.export_name, - })), - this.source + })) ); - const parts = module.split('✂]'); - const final_chunk = parts.pop(); - - const compiled = new Bundle({ separator: '' }); - - function add_string(str: string) { - compiled.addSource({ - content: new MagicString(str), - }); - } - - const { filename } = compile_options; - - // special case — the source file doesn't actually get used anywhere. we need - // to add an empty file to populate map.sources and map.sourcesContent - if (!parts.length) { - compiled.addSource({ - filename, - content: new MagicString(this.source).remove(0, this.source.length), - }); - } - - const pattern = /\[✂(\d+)-(\d+)$/; - - parts.forEach((str: string) => { - const chunk = str.replace(pattern, ''); - if (chunk) add_string(chunk); - - const match = pattern.exec(str); - - const snippet = this.code.snip(+match[1], +match[2]); - - compiled.addSource({ - filename, - content: snippet, - }); - }); - - add_string(final_chunk); - css = compile_options.customElement ? { code: null, map: null } : this.stylesheet.render(compile_options.cssOutputFilename, true); - js = { - code: compiled.toString(), - map: compiled.generateMap({ - includeContent: true, - file: compile_options.outputFilename, - }), - }; + js = print(program); } return { diff --git a/src/compiler/compile/create_module.ts b/src/compiler/compile/create_module.ts index 83ba38c510..2e25258835 100644 --- a/src/compiler/compile/create_module.ts +++ b/src/compiler/compile/create_module.ts @@ -1,7 +1,6 @@ -import deindent from './utils/deindent'; import list from '../utils/list'; import { ModuleFormat, Node, Identifier } from '../interfaces'; -import { stringify_props } from './utils/stringify_props'; +import { b, x } from 'code-red'; const wrappers = { esm, cjs }; @@ -11,24 +10,23 @@ interface Export { } export default function create_module( - code: string, + program: any, format: ModuleFormat, - name: string, + name: Identifier, banner: string, sveltePath = 'svelte', helpers: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>, imports: Node[], - module_exports: Export[], - source: string -): string { + module_exports: Export[] +) { const internal_path = `${sveltePath}/internal`; if (format === 'esm') { - return esm(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, source); + return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); } - if (format === 'cjs') return cjs(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); + if (format === 'cjs') return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); } @@ -40,54 +38,75 @@ function edit_source(source, sveltePath) { } function esm( - code: string, - name: string, - banner: string, + program: any, + name: Identifier, + _banner: string, sveltePath: string, internal_path: string, helpers: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>, imports: Node[], - module_exports: Export[], - source: string + module_exports: Export[] ) { - const internal_imports = helpers.length > 0 && ( - `import ${stringify_props(helpers.map(h => h.name === h.alias.name ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};` - ); - const internal_globals = globals.length > 0 && ( - `const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};` - ); - - const user_imports = imports.length > 0 && ( - imports - .map((declaration: Node) => { - const import_source = edit_source(declaration.source.value, sveltePath); - - return ( - source.slice(declaration.start, declaration.source.start) + - JSON.stringify(import_source) + - source.slice(declaration.source.end, declaration.end) - ); - }) - .join('\n') - ); - - return deindent` - ${banner} - ${internal_imports} + const import_declaration = { + type: 'ImportDeclaration', + specifiers: helpers.map(h => ({ + type: 'ImportSpecifier', + local: h.alias, + imported: { type: 'Identifier', name: h.name } + })), + source: { type: 'Literal', value: internal_path } + } + + const internal_globals = globals.length > 0 &&{ + type: 'VariableDeclaration', + kind: 'const', + declarations: [{ + id: { + type: 'ObjectPattern', + properties: globals.sort((a, b) => a.name < b.name ? -1 : 1).map(g => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + key: { type: 'Identifier', name: g.name }, + value: { type: 'Identifier', name: g.alias } + })) + }, + init: { type: 'Identifier', name: helpers.find(({ name }) => name === 'globals').alias } + }] + }; + + // edit user imports + imports.forEach(node => { + node.source.value = edit_source(node.source.value, sveltePath); + }); + + const exports = module_exports.length > 0 && { + type: 'ExportNamedDeclaration', + specifiers: module_exports.map(x => ({ + type: 'Specifier', + local: { type: 'Identifier', name: x.name }, + exported: { type: 'Identifier', name: x.as } + })) + }; + + program.body = b` + ${import_declaration} ${internal_globals} - ${user_imports} + ${imports} - ${code} + ${program.body} export default ${name}; - ${module_exports.length > 0 && `export { ${module_exports.map(e => e.name === e.as ? e.name : `${e.name} as ${e.as}`).join(', ')} };`}`; + ${exports} + `; } function cjs( - code: string, - name: string, - banner: string, + program: any, + name: Identifier, + _banner: string, sveltePath: string, internal_path: string, helpers: Array<{ name: string; alias: Identifier }>, @@ -95,52 +114,80 @@ function cjs( imports: Node[], module_exports: Export[] ) { - const declarations = helpers.map(h => `${h.alias.name === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort(); - - const internal_imports = helpers.length > 0 && ( - `const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n` - ); - const internal_globals = globals.length > 0 && ( - `const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};` - ); - - const requires = imports.map(node => { - let lhs; - - if (node.specifiers[0].type === 'ImportNamespaceSpecifier') { - lhs = node.specifiers[0].local.name; - } else { - const properties = node.specifiers.map(s => { - if (s.type === 'ImportDefaultSpecifier') { - return `default: ${s.local.name}`; - } - - return s.local.name === s.imported.name - ? s.local.name - : `${s.imported.name}: ${s.local.name}`; - }); - - lhs = `{ ${properties.join(', ')} }`; - } - - const source = edit_source(node.source.value, sveltePath); - - return `const ${lhs} = require("${source}");`; - }); - - const exports = [`exports.default = ${name};`].concat( - module_exports.map(x => `exports.${x.as} = ${x.name};`) - ); - - return deindent` - ${banner} + const internal_requires = { + type: 'VariableDeclaration', + kind: 'const', + declarations: [{ + type: 'VariableDeclarator', + id: { + type: 'ObjectPattern', + properties: helpers.sort((a, b) => a.name < b.name ? -1 : 1).map(h => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + key: { type: 'Identifier', name: h.name }, + value: h.alias, + kind: 'init' + })) + }, + init: x`require("${internal_path}")` + }] + }; + + const internal_globals = globals.length > 0 &&{ + type: 'VariableDeclaration', + kind: 'const', + declarations: [{ + type: 'VariableDeclarator', + id: { + type: 'ObjectPattern', + properties: globals.sort((a, b) => a.name < b.name ? -1 : 1).map(g => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + key: { type: 'Identifier', name: g.name }, + value: { type: 'Identifier', name: g.alias } + })) + }, + init: { type: 'Identifier', name: helpers.find(({ name }) => name === 'globals').alias } + }] + }; + + const user_requires = imports.map(node => ({ + type: 'VariableDeclaration', + kind: 'const', + declarations: [{ + type: 'VariableDeclarator', + id: node.specifiers[0].type === 'ImportNamespaceSpecifier' + ? { type: 'Identifier', name: node.specifiers[0].local.name } + : { + type: 'ObjectPattern', + properties: node.specifiers.map(s => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + key: s.imported || { type: 'Identifier', name: 'default' }, + value: s.local + })) + }, + init: x`require("${edit_source(node.source.value, sveltePath)}")` + }] + })); + + const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); + + program.body = b` "use strict"; + ${internal_requires} + // ${internal_globals} + ${user_requires} - ${internal_imports} - ${internal_globals} - ${requires} - - ${code} + ${program.body} - ${exports}`; + exports.default = ${name}; + ${exports} + `; } diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 746afd048f..e1cc3d6a3d 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -255,7 +255,7 @@ export default class Block { const properties: Record = {}; - const noop = x`noop`; + const noop = x`@noop`; properties.key = key properties.first = this.first; @@ -296,7 +296,7 @@ export default class Block { properties.mount = noop; } else { properties.mount = x`function mount(#target, anchor) { - //${this.chunks.mount} + ${this.chunks.mount} }`; } @@ -354,21 +354,22 @@ export default class Block { } const return_value: any = x`{ - // key: ${properties.key}, - // first: ${properties.first}, - // c: ${properties.create}, - // l: ${properties.claim}, - // h: ${properties.hydrate}, + key: ${properties.key}, + first: ${properties.first}, + c: ${properties.create}, + l: ${properties.claim}, + h: ${properties.hydrate}, m: ${properties.mount}, - // p: ${properties.update}, - // r: ${properties.measure}, - // f: ${properties.fix}, - // a: ${properties.animate}, - // i: ${properties.intro}, - // o: ${properties.outro}, - // d: ${properties.destroy} + p: ${properties.update}, + r: ${properties.measure}, + f: ${properties.fix}, + a: ${properties.animate}, + i: ${properties.intro}, + o: ${properties.outro}, + d: ${properties.destroy} }`; + // TODO should code-red do this automatically? probably return_value.properties = return_value.properties.filter(prop => prop.value); /* eslint-disable @typescript-eslint/indent,indent */ diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index c7ce2cac7b..6612895a25 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -390,7 +390,12 @@ export default function dom( `); } - const prop_names = x`[${props.map(v => ({ type: 'Literal', value: v.export_name }))}]`; + const prop_names = x`[]`; + + // TODO find a more idiomatic way of doing this + props.forEach(v => { + (prop_names as any).elements.push({ type: 'Literal', value: v.export_name }); + }); if (options.customElement) { body.push(b` @@ -453,5 +458,18 @@ export default function dom( `); } - return body; + return flatten(body, []); +} + +function flatten(nodes: any[], target: any[]) { + for (let i = 0; i < nodes.length; i += 1) { + const node = nodes[i]; + if (Array.isArray(node)) { + flatten(node, target); + } else { + target.push(node); + } + } + + return target; } diff --git a/test/runtime/index.js b/test/runtime/index.js index ae1b40847c..505ac4d139 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -3,7 +3,7 @@ import * as path from "path"; import * as fs from "fs"; import { rollup } from 'rollup'; import * as virtual from 'rollup-plugin-virtual'; -import { clear_loops, flush, set_now, set_raf } from "../../internal"; +import { clear_loops, flush, set_now, set_raf, component_subscribe } from "../../internal"; import { showOutput, @@ -33,9 +33,7 @@ describe("runtime", () => { require.extensions[".svelte"] = function(module, filename) { const options = Object.assign({ - filename, - format: 'cjs', - sveltePath + filename }, compileOptions); const { js: { code } } = compile(fs.readFileSync(filename, "utf-8"), options); @@ -72,6 +70,7 @@ describe("runtime", () => { const cwd = path.resolve(`test/runtime/samples/${dir}`); compileOptions = config.compileOptions || {}; + compileOptions.format = 'cjs'; compileOptions.sveltePath = sveltePath; compileOptions.hydratable = hydrate; compileOptions.immutable = config.immutable; diff --git a/test/runtime/samples/_/_config.js b/test/runtime/samples/_/_config.js index 7ba33fb008..e7538c4ecf 100644 --- a/test/runtime/samples/_/_config.js +++ b/test/runtime/samples/_/_config.js @@ -1,5 +1,6 @@ export default { solo: 1, + show: 1, html: '

Hello world!

' }; \ No newline at end of file