diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index e6d07de854c..b63faf7342e 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,5 +1,6 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION } from '../values'; +import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; @@ -9,6 +10,14 @@ export default class ArrayPattern extends NodeBase implements PatternNode { type: NodeType.tArrayPattern; elements: (PatternNode | null)[]; + addExportedVariables(variables: Variable[]): void { + for (const element of this.elements) { + if (element !== null) { + element.addExportedVariables(variables); + } + } + } + declare(kind: string, _init: ExpressionEntity) { for (const element of this.elements) { if (element !== null) { diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 3d7bfa21e2c..32dba815c7d 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -1,7 +1,9 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; +import { getSystemExportStatement } from '../../utils/systemJsRendering'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -47,12 +49,24 @@ export default class AssignmentExpression extends NodeBase { render(code: MagicString, options: RenderOptions) { this.left.render(code, options); this.right.render(code, options); - if (options.format === 'system' && this.left.variable && this.left.variable.exportName) { - code.prependLeft( - code.original.indexOf('=', this.left.end) + 1, - ` exports('${this.left.variable.exportName}',` - ); - code.appendLeft(this.right.end, `)`); + if (options.format === 'system') { + if (this.left.variable && this.left.variable.exportName) { + code.prependLeft( + code.original.indexOf('=', this.left.end) + 1, + ` exports('${this.left.variable.exportName}',` + ); + code.appendLeft(this.right.end, `)`); + } else if ('addExportedVariables' in this.left) { + const systemPatternExports: Variable[] = []; + this.left.addExportedVariables(systemPatternExports); + if (systemPatternExports.length > 0) { + code.prependRight( + this.start, + `function (v) {${getSystemExportStatement(systemPatternExports)} return v;} (` + ); + code.appendLeft(this.end, ')'); + } + } } } } diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index a8a599f4b68..3da429d404a 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -3,6 +3,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -13,6 +14,10 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { left: PatternNode; right: ExpressionNode; + addExportedVariables(variables: Variable[]): void { + this.left.addExportedVariables(variables); + } + bind() { super.bind(); this.left.deoptimizePath(EMPTY_PATH); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 3d7241ad368..c0a60afd1db 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -13,18 +13,25 @@ import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { Node, NodeBase } from './shared/Node'; +import { PatternNode } from './shared/Pattern'; export function isIdentifier(node: Node): node is Identifier { return node.type === NodeType.Identifier; } -export default class Identifier extends NodeBase { +export default class Identifier extends NodeBase implements PatternNode { type: NodeType.tIdentifier; name: string; variable: Variable; private bound: boolean; + addExportedVariables(variables: Variable[]): void { + if (this.variable.exportName) { + variables.push(this.variable); + } + } + bind() { if (this.bound) return; this.bound = true; diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index cae7d9ee7ed..a2ceed3e817 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,5 +1,6 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath } from '../values'; +import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import Property from './Property'; import RestElement from './RestElement'; @@ -11,6 +12,16 @@ export default class ObjectPattern extends NodeBase implements PatternNode { type: NodeType.tObjectPattern; properties: (Property | RestElement)[]; + addExportedVariables(variables: Variable[]): void { + for (const property of this.properties) { + if (property.type === NodeType.Property) { + ((property.value)).addExportedVariables(variables); + } else { + property.argument.addExportedVariables(variables); + } + } + } + declare(kind: string, init: ExpressionEntity) { for (const property of this.properties) { property.declare(kind, init); diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index d691fcdbdcd..5de35c80134 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,5 +1,6 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY } from '../values'; +import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; @@ -11,6 +12,10 @@ export default class RestElement extends NodeBase implements PatternNode { private declarationInit: ExpressionEntity | null = null; + addExportedVariables(variables: Variable[]): void { + this.argument.addExportedVariables(variables); + } + bind() { super.bind(); if (this.declarationInit !== null) { diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 36216bb4cda..837f6b03a9f 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -5,12 +5,13 @@ import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; +import { getSystemExportStatement } from '../../utils/systemJsRendering'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_PATH, ObjectPath } from '../values'; import Variable from '../variables/Variable'; import { isIdentifier } from './Identifier'; import * as NodeType from './NodeType'; -import { Node, NodeBase } from './shared/Node'; +import { NodeBase } from './shared/Node'; import VariableDeclarator from './VariableDeclarator'; function isReassignedExportsMember(variable: Variable): boolean { @@ -22,8 +23,20 @@ function isReassignedExportsMember(variable: Variable): boolean { ); } -export function isVariableDeclaration(node: Node): node is VariableDeclaration { - return node.type === NodeType.VariableDeclaration; +function areAllDeclarationsIncludedAndNotExported(declarations: VariableDeclarator[]): boolean { + for (const declarator of declarations) { + if (!declarator.included) { + return false; + } + if (declarator.id.type === NodeType.Identifier) { + if (declarator.id.variable.exportName) return false; + } else { + const exportedVariables: Variable[] = []; + declarator.id.addExportedVariables(exportedVariables); + if (exportedVariables.length > 0) return false; + } + } + return true; } export default class VariableDeclaration extends NodeBase { @@ -64,12 +77,7 @@ export default class VariableDeclaration extends NodeBase { } render(code: MagicString, options: RenderOptions, nodeRenderOptions: NodeRenderOptions = BLANK) { - if ( - this.declarations.every( - declarator => - declarator.included && (!declarator.id.variable || !declarator.id.variable.exportName) - ) - ) { + if (areAllDeclarationsIncludedAndNotExported(this.declarations)) { for (const declarator of this.declarations) { declarator.render(code, options); } @@ -88,7 +96,7 @@ export default class VariableDeclaration extends NodeBase { code: MagicString, options: RenderOptions, { start = this.start, end = this.end, isNoStatement }: NodeRenderOptions - ) { + ): void { const separatedNodes = getCommaSeparatedNodesWithBoundaries( this.declarations, code, @@ -108,6 +116,7 @@ export default class VariableDeclaration extends NodeBase { let separatorString = '', leadingString, nextSeparatorString; + const systemPatternExports: Variable[] = []; for (const { node, start, separator, contentEnd, end } of separatedNodes) { if ( !node.included || @@ -124,17 +133,16 @@ export default class VariableDeclaration extends NodeBase { } isInDeclaration = false; } else { - if ( - options.format === 'system' && - node.init !== null && - isIdentifier(node.id) && - node.id.variable.exportName - ) { - code.prependLeft( - code.original.indexOf('=', node.id.end) + 1, - ` exports('${node.id.variable.safeExportName || node.id.variable.exportName}',` - ); - nextSeparatorString += ')'; + if (options.format === 'system' && node.init !== null) { + if (node.id.type !== NodeType.Identifier) { + node.id.addExportedVariables(systemPatternExports); + } else if (node.id.variable.exportName) { + code.prependLeft( + code.original.indexOf('=', node.id.end) + 1, + ` exports('${node.id.variable.safeExportName || node.id.variable.exportName}',` + ); + nextSeparatorString += ')'; + } } if (isInDeclaration) { separatorString += ','; @@ -166,7 +174,8 @@ export default class VariableDeclaration extends NodeBase { lastSeparatorPos, actualContentEnd, renderedContentEnd, - !isNoStatement + !isNoStatement, + systemPatternExports ); } else { code.remove(start, end); @@ -179,8 +188,9 @@ export default class VariableDeclaration extends NodeBase { lastSeparatorPos: number, actualContentEnd: number, renderedContentEnd: number, - addSemicolon: boolean - ) { + addSemicolon: boolean, + systemPatternExports: Variable[] + ): void { if (code.original.charCodeAt(this.end - 1) === 59 /*";"*/) { code.remove(this.end - 1, this.end); } @@ -207,6 +217,8 @@ export default class VariableDeclaration extends NodeBase { } else { code.appendLeft(renderedContentEnd, separatorString); } - return separatorString; + if (systemPatternExports.length > 0) { + code.appendLeft(renderedContentEnd, ' ' + getSystemExportStatement(systemPatternExports)); + } } } diff --git a/src/ast/nodes/shared/Pattern.ts b/src/ast/nodes/shared/Pattern.ts index 4d6466a07f1..69bb848b781 100644 --- a/src/ast/nodes/shared/Pattern.ts +++ b/src/ast/nodes/shared/Pattern.ts @@ -1,4 +1,7 @@ import { WritableEntity } from '../../Entity'; +import Variable from '../../variables/Variable'; import { Node } from './Node'; -export interface PatternNode extends WritableEntity, Node {} +export interface PatternNode extends WritableEntity, Node { + addExportedVariables(variables: Variable[]): void; +} diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 354dd8e631a..14bc28f7bcd 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -237,7 +237,6 @@ export interface InputOptions { moduleContext?: string | ((id: string) => string) | { [id: string]: string }; watch?: WatcherOptions; experimentalCodeSplitting?: boolean; - experimentalDynamicImport?: boolean; experimentalTopLevelAwait?: boolean; inlineDynamicImports?: boolean; preserveSymlinks?: boolean; diff --git a/src/utils/systemJsRendering.ts b/src/utils/systemJsRendering.ts new file mode 100644 index 00000000000..15a3ade830f --- /dev/null +++ b/src/utils/systemJsRendering.ts @@ -0,0 +1,12 @@ +import Variable from '../ast/variables/Variable'; + +export function getSystemExportStatement(exportedVariables: Variable[]): string { + if (exportedVariables.length === 1) { + return `exports('${exportedVariables[0].safeExportName || + exportedVariables[0].exportName}', ${exportedVariables[0].getName()});`; + } else { + return `exports({${exportedVariables + .map(variable => `${variable.safeExportName || variable.exportName}: ${variable.getName()}`) + .join(', ')}});`; + } +} diff --git a/test/form/samples/system-export-destructuring-assignment/_config.js b/test/form/samples/system-export-destructuring-assignment/_config.js new file mode 100644 index 00000000000..82ec35dd48d --- /dev/null +++ b/test/form/samples/system-export-destructuring-assignment/_config.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'supports destructuring assignments of exports for systemJS', + options: { + output: { + format: 'system' + } + } +}; diff --git a/test/form/samples/system-export-destructuring-assignment/_expected.js b/test/form/samples/system-export-destructuring-assignment/_expected.js new file mode 100644 index 00000000000..d4efb012846 --- /dev/null +++ b/test/form/samples/system-export-destructuring-assignment/_expected.js @@ -0,0 +1,19 @@ +System.register([], function (exports, module) { + 'use strict'; + return { + execute: function () { + + exports({ + a: void 0, + b: void 0, + c: void 0 + }); + + let a, b, c; + + console.log(function (v) {exports('a', a); return v;} ({a} = someObject)); + (function (v) {exports({b: b, c: c}); return v;} ({b, c} = someObject)); + + } + }; +}); diff --git a/test/form/samples/system-export-destructuring-assignment/main.js b/test/form/samples/system-export-destructuring-assignment/main.js new file mode 100644 index 00000000000..5f3831cdff7 --- /dev/null +++ b/test/form/samples/system-export-destructuring-assignment/main.js @@ -0,0 +1,4 @@ +export let a, b, c; + +console.log({a} = someObject); +({b, c} = someObject); diff --git a/test/form/samples/system-export-destructuring-declaration/_config.js b/test/form/samples/system-export-destructuring-declaration/_config.js new file mode 100644 index 00000000000..db198f829d5 --- /dev/null +++ b/test/form/samples/system-export-destructuring-declaration/_config.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'supports destructuring declarations for systemJS', + options: { + output: { + format: 'system' + } + } +}; diff --git a/test/form/samples/system-export-destructuring-declaration/_expected.js b/test/form/samples/system-export-destructuring-declaration/_expected.js new file mode 100644 index 00000000000..32d922fd76f --- /dev/null +++ b/test/form/samples/system-export-destructuring-declaration/_expected.js @@ -0,0 +1,15 @@ +System.register([], function (exports, module) { + 'use strict'; + return { + execute: function () { + + const {a = 1, ...b} = global1, c = exports('c', global2), {d} = global3; exports({a: a, b: b, d: d}); + const [e, ...f] = global4; exports({e: e, f: f}); + const {g, x: h = 2, y: {z: i}, a: [j ,k,, l]} = global5; exports({g: g, h: h, i: i, j: j, k: k, l: l}); + + var m = exports('m', 1); + var {m} = global6; exports('m', m); + + } + }; +}); diff --git a/test/form/samples/system-export-destructuring-declaration/main.js b/test/form/samples/system-export-destructuring-declaration/main.js new file mode 100644 index 00000000000..aacc41461f2 --- /dev/null +++ b/test/form/samples/system-export-destructuring-declaration/main.js @@ -0,0 +1,6 @@ +export const {a = 1, ...b} = global1, c = global2, {d} = global3; +export const [e, ...f] = global4; +export const {g, x: h = 2, y: {z: i}, a: [j ,k,, l]} = global5; + +export var m = 1; +var {m} = global6;