diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index b94b108f43..fa5a8d555e 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -24,7 +24,7 @@ import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; import get_object from './utils/get_object'; import Slot from './nodes/Slot'; -import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree'; +import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal, ExportDefaultDeclaration, ExportAllDeclaration } from 'estree'; import add_to_set from './utils/add_to_set'; import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; @@ -70,6 +70,8 @@ export default class Component { var_lookup: Map = new Map(); imports: ImportDeclaration[] = []; + exports_from: ExportNamedDeclaration[] = []; + instance_exports_from: ExportNamedDeclaration[] = []; hoistable_nodes: Set = new Set(); node_for_declaration: Map = new Map(); @@ -333,7 +335,8 @@ export default class Component { .map(variable => ({ name: variable.name, as: variable.export_name - })) + })), + this.exports_from ); css = compile_options.customElement @@ -492,22 +495,27 @@ export default class Component { this.imports.push(node); } - extract_exports(node) { + extract_exports(node, module_script = false) { const ignores = extract_svelte_ignore_from_comments(node); if (ignores.length) this.push_ignores(ignores); - const result = this._extract_exports(node); + const result = this._extract_exports(node, module_script); if (ignores.length) this.pop_ignores(); return result; } - private _extract_exports(node) { + private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script) { if (node.type === 'ExportDefaultDeclaration') { - return this.error(node, compiler_errors.default_export); + return this.error(node as any, compiler_errors.default_export); } if (node.type === 'ExportNamedDeclaration') { if (node.source) { - return this.error(node, compiler_errors.not_implemented); + if (module_script) { + this.exports_from.push(node); + } else { + this.instance_exports_from.push(node); + } + return null; } if (node.declaration) { if (node.declaration.type === 'VariableDeclaration') { @@ -516,7 +524,7 @@ export default class Component { const variable = this.var_lookup.get(name); variable.export_name = name; if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { - this.warn(declarator, compiler_warnings.unused_export_let(this.name.name, name)); + this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name)); } }); }); @@ -536,7 +544,7 @@ export default class Component { variable.export_name = specifier.exported.name; if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { - this.warn(specifier, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)); + this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)); } } }); @@ -612,7 +620,7 @@ export default class Component { } if (/^Export/.test(node.type)) { - const replacement = this.extract_exports(node); + const replacement = this.extract_exports(node, true); if (replacement) { body[i] = replacement; } else { diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 7ef06c0bbb..fac7029c80 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -174,10 +174,6 @@ export default { code: 'default-export', message: 'A component cannot have a default export' }, - not_implemented: { - code: 'not-implemented', - message: 'A component currently cannot have an export ... from' - }, illegal_declaration: { code: 'illegal-declaration', message: 'The $ prefix is reserved, and cannot be used for variable and import names' diff --git a/src/compiler/compile/create_module.ts b/src/compiler/compile/create_module.ts index 80e6308263..037b2b396e 100644 --- a/src/compiler/compile/create_module.ts +++ b/src/compiler/compile/create_module.ts @@ -1,7 +1,7 @@ import list from '../utils/list'; import { ModuleFormat } from '../interfaces'; import { b, x } from 'code-red'; -import { Identifier, ImportDeclaration } from 'estree'; +import { Identifier, ImportDeclaration, ExportNamedDeclaration } from 'estree'; const wrappers = { esm, cjs }; @@ -19,20 +19,21 @@ export default function create_module( helpers: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>, imports: ImportDeclaration[], - module_exports: Export[] + module_exports: Export[], + exports_from: ExportNamedDeclaration[] ) { const internal_path = `${sveltePath}/internal`; helpers.sort((a, b) => (a.name < b.name) ? -1 : 1); globals.sort((a, b) => (a.name < b.name) ? -1 : 1); + + const formatter = wrappers[format]; - if (format === 'esm') { - return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); + if (!formatter) { + throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); } - 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))})`); + return formatter(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, exports_from); } function edit_source(source, sveltePath) { @@ -76,7 +77,8 @@ function esm( helpers: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>, imports: ImportDeclaration[], - module_exports: Export[] + module_exports: Export[], + exports_from: ExportNamedDeclaration[] ) { const import_declaration = { type: 'ImportDeclaration', @@ -94,6 +96,9 @@ function esm( imports.forEach(node => { node.source.value = edit_source(node.source.value, sveltePath); }); + exports_from.forEach(node => { + node.source!.value = edit_source(node.source!.value, sveltePath); + }); const exports = module_exports.length > 0 && { type: 'ExportNamedDeclaration', @@ -110,6 +115,7 @@ function esm( ${import_declaration} ${internal_globals} ${imports} + ${exports_from} ${program.body} @@ -127,7 +133,8 @@ function cjs( helpers: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>, imports: ImportDeclaration[], - module_exports: Export[] + module_exports: Export[], + exports_from: ExportNamedDeclaration[] ) { const internal_requires = { type: 'VariableDeclaration', @@ -183,6 +190,13 @@ function cjs( const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); + const user_exports_from = exports_from.map(node => { + const init = x`require("${edit_source(node.source.value, sveltePath)}")`; + return node.specifiers.map(specifier => { + return b`exports.${specifier.exported} = ${init}.${specifier.local};`; + }); + }); + program.body = b` /* ${banner} */ @@ -190,6 +204,7 @@ function cjs( ${internal_requires} ${internal_globals} ${user_requires} + ${user_exports_from} ${program.body} diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 535983cfd0..f74f4cdf1c 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -6,7 +6,7 @@ import { walk } from 'estree-walker'; import { extract_names, Scope } from 'periscopic'; import { invalidate } from './invalidate'; import Block from './Block'; -import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; +import { ImportDeclaration, ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; import { apply_preprocessor_sourcemap } from '../../utils/mapped_code'; import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; import { flatten } from '../../utils/flatten'; @@ -174,6 +174,46 @@ export default function dom( } }); + component.instance_exports_from.forEach(exports_from => { + const import_declaration = { + ...exports_from, + type: 'ImportDeclaration', + specifiers: [], + source: exports_from.source + }; + component.imports.push(import_declaration as ImportDeclaration); + + exports_from.specifiers.forEach(specifier => { + if (component.component_options.accessors) { + const name = component.get_unique_name(specifier.exported.name); + import_declaration.specifiers.push({ + ...specifier, + type: 'ImportSpecifier', + imported: specifier.local, + local: name + }); + + accessors.push({ + type: 'MethodDefinition', + kind: 'get', + key: { type: 'Identifier', name: specifier.exported.name }, + value: x`function() { + return ${name} + }` + }); + } else if (component.compile_options.dev) { + accessors.push({ + type: 'MethodDefinition', + kind: 'get', + key: { type: 'Identifier', name: specifier.exported.name }, + value: x`function() { + throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''"); + }` + }); + } + }); + }); + if (component.compile_options.dev) { // checking that expected ones were passed const expected = props.filter(prop => prop.writable && !prop.initialised); diff --git a/test/js/samples/export-from-accessors/_config.js b/test/js/samples/export-from-accessors/_config.js new file mode 100644 index 0000000000..7f9293a560 --- /dev/null +++ b/test/js/samples/export-from-accessors/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + accessors: true + } +}; diff --git a/test/js/samples/export-from-accessors/expected.js b/test/js/samples/export-from-accessors/expected.js new file mode 100644 index 0000000000..20b0524ca7 --- /dev/null +++ b/test/js/samples/export-from-accessors/expected.js @@ -0,0 +1,34 @@ +/* generated by Svelte vX.Y.Z */ +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; + +import { f as f_1, g as g_1 } from './d'; +import { h as h_1 } from './e'; +import { i as j } from './f'; +export { d as e } from './c'; +export { c } from './b'; +export { a, b } from './a'; + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, null, safe_not_equal, {}); + } + + get f() { + return f_1; + } + + get g() { + return g_1; + } + + get h() { + return h_1; + } + + get j() { + return j; + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/export-from-accessors/input.svelte b/test/js/samples/export-from-accessors/input.svelte new file mode 100644 index 0000000000..66957806a2 --- /dev/null +++ b/test/js/samples/export-from-accessors/input.svelte @@ -0,0 +1,11 @@ + + + diff --git a/test/js/samples/export-from-cjs/_config.js b/test/js/samples/export-from-cjs/_config.js new file mode 100644 index 0000000000..2506f1d5fc --- /dev/null +++ b/test/js/samples/export-from-cjs/_config.js @@ -0,0 +1,6 @@ +export default { + options: { + accessors: true, + format: 'cjs' + } +}; diff --git a/test/js/samples/export-from-cjs/expected.js b/test/js/samples/export-from-cjs/expected.js new file mode 100644 index 0000000000..d40f986635 --- /dev/null +++ b/test/js/samples/export-from-cjs/expected.js @@ -0,0 +1,36 @@ +/* generated by Svelte vX.Y.Z */ +"use strict"; + +const { SvelteComponent, init, safe_not_equal } = require("svelte/internal"); +const { f: f_1, g: g_1 } = require("./d"); +const { h: h_1 } = require("./e"); +const { i: j } = require("./f"); +exports.e = require("./c").d; +exports.c = require("./b").c; +exports.a = require("./a").a; +exports.b = require("./a").b; + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, null, safe_not_equal, {}); + } + + get f() { + return f_1; + } + + get g() { + return g_1; + } + + get h() { + return h_1; + } + + get j() { + return j; + } +} + +exports.default = Component; \ No newline at end of file diff --git a/test/js/samples/export-from-cjs/input.svelte b/test/js/samples/export-from-cjs/input.svelte new file mode 100644 index 0000000000..66957806a2 --- /dev/null +++ b/test/js/samples/export-from-cjs/input.svelte @@ -0,0 +1,11 @@ + + + diff --git a/test/js/samples/export-from/expected.js b/test/js/samples/export-from/expected.js new file mode 100644 index 0000000000..04fb47605f --- /dev/null +++ b/test/js/samples/export-from/expected.js @@ -0,0 +1,18 @@ +/* generated by Svelte vX.Y.Z */ +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; + +import './d'; +import './e'; +import './f'; +export { d as e } from './c'; +export { c } from './b'; +export { a, b } from './a'; + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, null, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/export-from/input.svelte b/test/js/samples/export-from/input.svelte new file mode 100644 index 0000000000..66957806a2 --- /dev/null +++ b/test/js/samples/export-from/input.svelte @@ -0,0 +1,11 @@ + + + diff --git a/test/runtime/samples/export-from/A.svelte b/test/runtime/samples/export-from/A.svelte new file mode 100644 index 0000000000..7773727704 --- /dev/null +++ b/test/runtime/samples/export-from/A.svelte @@ -0,0 +1,23 @@ + + + + +a: {typeof a}
+b: {typeof b}
+c: {typeof c}
+d: {typeof d}
+e: {typeof e}
+f: {typeof f}
+g: {typeof g}
\ No newline at end of file diff --git a/test/runtime/samples/export-from/B.svelte b/test/runtime/samples/export-from/B.svelte new file mode 100644 index 0000000000..0cc1070cb5 --- /dev/null +++ b/test/runtime/samples/export-from/B.svelte @@ -0,0 +1,8 @@ + diff --git a/test/runtime/samples/export-from/_config.js b/test/runtime/samples/export-from/_config.js new file mode 100644 index 0000000000..9e65b7501d --- /dev/null +++ b/test/runtime/samples/export-from/_config.js @@ -0,0 +1,28 @@ +export default { + html: ` + a,b,undefined,c +
+ a: undefined
+ b: number
+ c: undefined
+ d: undefined
+ e: number
+ f: undefined
+ g: undefined
+
+ {"d":"d","e":"e","g":"f"} + `, + ssrHtml: ` + a,b,undefined,c +
+ a: undefined
+ b: number
+ c: undefined
+ d: undefined
+ e: number
+ f: undefined
+ g: undefined
+
+ {} + ` +}; diff --git a/test/runtime/samples/export-from/main.svelte b/test/runtime/samples/export-from/main.svelte new file mode 100644 index 0000000000..18cc066c1f --- /dev/null +++ b/test/runtime/samples/export-from/main.svelte @@ -0,0 +1,21 @@ + + +{a},{b},{c},{d} +
+ +
+{JSON.stringify(props)} \ No newline at end of file