Skip to content

Commit

Permalink
[feat] enable export ... from (#6574)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau authored Jul 27, 2021
1 parent 4d677d5 commit c550f60
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 24 deletions.
28 changes: 18 additions & 10 deletions src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -70,6 +70,8 @@ export default class Component {
var_lookup: Map<string, Var> = new Map();

imports: ImportDeclaration[] = [];
exports_from: ExportNamedDeclaration[] = [];
instance_exports_from: ExportNamedDeclaration[] = [];

hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map();
Expand Down Expand Up @@ -333,7 +335,8 @@ export default class Component {
.map(variable => ({
name: variable.name,
as: variable.export_name
}))
})),
this.exports_from
);

css = compile_options.customElement
Expand Down Expand Up @@ -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') {
Expand All @@ -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));
}
});
});
Expand All @@ -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));
}
}
});
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/compile/compiler_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
33 changes: 24 additions & 9 deletions src/compiler/compile/create_module.ts
Original file line number Diff line number Diff line change
@@ -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 };

Expand All @@ -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) {
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -110,6 +115,7 @@ function esm(
${import_declaration}
${internal_globals}
${imports}
${exports_from}
${program.body}
Expand All @@ -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',
Expand Down Expand Up @@ -183,13 +190,21 @@ 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} */
"use strict";
${internal_requires}
${internal_globals}
${user_requires}
${user_exports_from}
${program.body}
Expand Down
42 changes: 41 additions & 1 deletion src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 '<svelte:options accessors/>'");
}`
});
}
});
});

if (component.compile_options.dev) {
// checking that expected ones were passed
const expected = props.filter(prop => prop.writable && !prop.initialised);
Expand Down
5 changes: 5 additions & 0 deletions test/js/samples/export-from-accessors/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
options: {
accessors: true
}
};
34 changes: 34 additions & 0 deletions test/js/samples/export-from-accessors/expected.js
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions test/js/samples/export-from-accessors/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script context="module">
export { a, b } from './a';
export { c } from './b';
export { d as e } from './c';
</script>

<script>
export { f, g } from './d';
export { h } from './e';
export { i as j } from './f';
</script>
6 changes: 6 additions & 0 deletions test/js/samples/export-from-cjs/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
options: {
accessors: true,
format: 'cjs'
}
};
36 changes: 36 additions & 0 deletions test/js/samples/export-from-cjs/expected.js
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions test/js/samples/export-from-cjs/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script context="module">
export { a, b } from './a';
export { c } from './b';
export { d as e } from './c';
</script>

<script>
export { f, g } from './d';
export { h } from './e';
export { i as j } from './f';
</script>
18 changes: 18 additions & 0 deletions test/js/samples/export-from/expected.js
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions test/js/samples/export-from/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script context="module">
export { a, b } from './a';
export { c } from './b';
export { d as e } from './c';
</script>

<script>
export { f, g } from './d';
export { h } from './e';
export { i as j } from './f';
</script>
Loading

0 comments on commit c550f60

Please sign in to comment.