diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md
index a65995864de..7b2a6562472 100644
--- a/docs/05-plugin-development.md
+++ b/docs/05-plugin-development.md
@@ -169,7 +169,7 @@ Phase: `generate`
Cf. [`output.intro/output.outro`](guide/en/#outputintrooutputoutro).
#### `load`
-Type: `(id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null }`
+Type: `(id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null }`
Kind: `async, first`
Phase: `build`
@@ -177,7 +177,25 @@ Defines a custom loader. Returning `null` defers to other `load` functions (and
If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included in the bundle without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`. The `transform` hook can override this.
-You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--moduleinfo) to find out the previous value of `moduleSideEffects` inside this hook.
+If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. The `transform` hook can override this. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:
+
+**dep.js: (`{syntheticNamedExports: true}`)**
+
+```
+export default {
+ foo: 42,
+ bar: 'hello'
+}
+```
+
+**main.js: (entry point)**
+
+```js
+import { foo, bar } from './dep.js'
+console.log(foo, bar);
+```
+
+You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--moduleinfo) to find out the previous values of `moduleSideEffects` and `syntheticNamedExports` inside this hook.
#### `options`
Type: `(options: InputOptions) => InputOptions | null`
@@ -268,7 +286,7 @@ resolveFileUrl({fileName}) {
```
#### `resolveId`
-Type: `(source: string, importer: string) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null}`
+Type: `(source: string, importer: string) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`
Kind: `async, first`
Phase: `build`
@@ -289,6 +307,24 @@ Relative ids, i.e. starting with `./` or `../`, will **not** be renormalized whe
If `false` is returned for `moduleSideEffects` in the first hook that resolves a module id and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the `treeshake.moduleSideEffects` option or default to `true`. The `load` and `transform` hooks can override this.
+If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. The `load` and `transform` hooks can override this. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:
+
+**dep.js: (`{syntheticNamedExports: true}`)**
+
+```
+export default {
+ foo: 42,
+ bar: 'hello'
+}
+```
+
+**main.js: (entry point)**
+
+```js
+import { foo, bar } from './dep.js'
+console.log(foo, bar);
+```
+
#### `resolveImportMeta`
Type: `(property: string | null, {chunkId: string, moduleId: string, format: string}) => string | null`
Kind: `sync, first`
@@ -313,7 +349,7 @@ resolveImportMeta(property, {moduleId}) {
Note that since this hook has access to the filename of the current chunk, its return value will not be considered when generating the hash of this chunk.
#### `transform`
-Type: `(code: string, id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null }`
+Type: `(code: string, id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null }`
Kind: `async, sequential`
Phase: `build`
@@ -323,6 +359,24 @@ Note that in watch mode, the result of this hook is cached when rebuilding and t
If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`.
+If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:
+
+**dep.js: (`{syntheticNamedExports: true}`)**
+
+```
+export default {
+ foo: 42,
+ bar: 'hello'
+}
+```
+
+**main.js: (entry point)**
+
+```js
+import { foo, bar } from './dep.js'
+console.log(foo, bar);
+```
+
You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--moduleinfo) to find out the previous value of `moduleSideEffects` inside this hook.
#### `watchChange`
diff --git a/src/Module.ts b/src/Module.ts
index 790af80d82f..60fb236bed8 100644
--- a/src/Module.ts
+++ b/src/Module.ts
@@ -22,9 +22,11 @@ import TemplateLiteral from './ast/nodes/TemplateLiteral';
import VariableDeclaration from './ast/nodes/VariableDeclaration';
import ModuleScope from './ast/scopes/ModuleScope';
import { PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker';
+import ExportDefaultVariable from './ast/variables/ExportDefaultVariable';
import ExportShimVariable from './ast/variables/ExportShimVariable';
import ExternalVariable from './ast/variables/ExternalVariable';
import NamespaceVariable from './ast/variables/NamespaceVariable';
+import SyntheticNamedExportVariable from './ast/variables/SyntheticNamedExportVariable';
import Variable from './ast/variables/Variable';
import Chunk from './Chunk';
import ExternalModule from './ExternalModule';
@@ -39,7 +41,7 @@ import {
RollupWarning,
TransformModuleJSON
} from './rollup/types';
-import { error } from './utils/error';
+import { error, Errors } from './utils/error';
import getCodeFrame from './utils/getCodeFrame';
import { getOriginalLocation } from './utils/getOriginalLocation';
import { makeLegal } from './utils/identifierHelpers';
@@ -206,6 +208,7 @@ export default class Module {
scope!: ModuleScope;
sourcemapChain!: DecodedSourceMapOrMissing[];
sources = new Set();
+ syntheticNamedExports: boolean;
transformFiles?: EmittedFile[];
userChunkNames = new Set();
usesTopLevelAwait = false;
@@ -214,19 +217,28 @@ export default class Module {
private ast!: Program;
private astContext!: AstContext;
private context: string;
+ private defaultExport: ExportDefaultVariable | null | undefined = null;
private esTreeAst!: ESTree.Program;
private graph: Graph;
private magicString!: MagicString;
private namespaceVariable: NamespaceVariable | null = null;
+ private syntheticExports = new Map();
private transformDependencies: string[] = [];
private transitiveReexports: string[] | null = null;
- constructor(graph: Graph, id: string, moduleSideEffects: boolean, isEntry: boolean) {
+ constructor(
+ graph: Graph,
+ id: string,
+ moduleSideEffects: boolean,
+ syntheticNamedExports: boolean,
+ isEntry: boolean
+ ) {
this.id = id;
this.graph = graph;
this.excludeFromSourcemap = /\0/.test(id);
this.context = graph.getModuleContext(id);
this.moduleSideEffects = moduleSideEffects;
+ this.syntheticNamedExports = syntheticNamedExports;
this.isEntryPoint = isEntry;
}
@@ -299,6 +311,21 @@ export default class Module {
return allExportNames;
}
+ getDefaultExport() {
+ if (this.defaultExport === null) {
+ this.defaultExport = undefined;
+ this.defaultExport = this.getVariableForExportName('default') as ExportDefaultVariable;
+ }
+ if (!this.defaultExport) {
+ return error({
+ code: Errors.SYNTHETIC_NAMED_EXPORTS_NEED_DEFAULT,
+ id: this.id,
+ message: `Modules with 'syntheticNamedExports' need a default export.`
+ });
+ }
+ return this.defaultExport;
+ }
+
getDynamicImportExpressions(): (string | Node)[] {
return this.dynamicImports.map(({ node }) => {
const importArgument = node.source;
@@ -342,7 +369,7 @@ export default class Module {
getOrCreateNamespace(): NamespaceVariable {
if (!this.namespaceVariable) {
- this.namespaceVariable = new NamespaceVariable(this.astContext);
+ this.namespaceVariable = new NamespaceVariable(this.astContext, this.syntheticNamedExports);
this.namespaceVariable.initialise();
}
return this.namespaceVariable;
@@ -439,9 +466,22 @@ export default class Module {
// we don't want to create shims when we are just
// probing export * modules for exports
- if (this.graph.shimMissingExports && !isExportAllSearch) {
- this.shimMissingExport(name);
- return this.exportShimVariable;
+ if (!isExportAllSearch) {
+ if (this.syntheticNamedExports) {
+ let syntheticExport = this.syntheticExports.get(name);
+ if (!syntheticExport) {
+ const defaultExport = this.getDefaultExport();
+ syntheticExport = new SyntheticNamedExportVariable(this.astContext, name, defaultExport);
+ this.syntheticExports.set(name, syntheticExport);
+ return syntheticExport;
+ }
+ return syntheticExport;
+ }
+
+ if (this.graph.shimMissingExports) {
+ this.shimMissingExport(name);
+ return this.exportShimVariable;
+ }
}
return undefined as any;
}
@@ -534,6 +574,7 @@ export default class Module {
originalSourcemap,
resolvedIds,
sourcemapChain,
+ syntheticNamedExports,
transformDependencies,
transformFiles
}: TransformModuleJSON & {
@@ -551,6 +592,9 @@ export default class Module {
if (typeof moduleSideEffects === 'boolean') {
this.moduleSideEffects = moduleSideEffects;
}
+ if (typeof syntheticNamedExports === 'boolean') {
+ this.syntheticNamedExports = syntheticNamedExports;
+ }
timeStart('generate ast', 3);
@@ -633,6 +677,7 @@ export default class Module {
originalSourcemap: this.originalSourcemap,
resolvedIds: this.resolvedIds,
sourcemapChain: this.sourcemapChain,
+ syntheticNamedExports: this.syntheticNamedExports,
transformDependencies: this.transformDependencies,
transformFiles: this.transformFiles
};
diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts
index faafbc9ac50..56e65b0a731 100644
--- a/src/ModuleLoader.ts
+++ b/src/ModuleLoader.ts
@@ -17,6 +17,7 @@ import {
errBadLoader,
errCannotAssignModuleToChunk,
errEntryCannotBeExternal,
+ errExternalSyntheticExports,
errInternalIdCannotBeExternal,
errInvalidOption,
errNamespaceConflict,
@@ -243,7 +244,7 @@ export class ModuleLoader {
module.id,
(module.resolvedIds[source] =
module.resolvedIds[source] ||
- this.handleMissingImports(await this.resolveId(source, module.id), source, module.id))
+ this.handleResolveId(await this.resolveId(source, module.id), source, module.id))
)
) as Promise[]),
...module.getDynamicImportExpressions().map((specifier, index) =>
@@ -272,6 +273,7 @@ export class ModuleLoader {
id: string,
importer: string,
moduleSideEffects: boolean,
+ syntheticNamedExports: boolean,
isEntry: boolean
): Promise {
const existingModule = this.modulesById.get(id);
@@ -280,7 +282,13 @@ export class ModuleLoader {
return Promise.resolve(existingModule);
}
- const module: Module = new Module(this.graph, id, moduleSideEffects, isEntry);
+ const module: Module = new Module(
+ this.graph,
+ id,
+ moduleSideEffects,
+ syntheticNamedExports,
+ isEntry
+ );
this.modulesById.set(id, module);
this.graph.watchFiles[id] = true;
const manualChunkAlias = this.getManualChunk(id);
@@ -321,6 +329,9 @@ export class ModuleLoader {
if (typeof sourceDescription.moduleSideEffects === 'boolean') {
module.moduleSideEffects = sourceDescription.moduleSideEffects;
}
+ if (typeof sourceDescription.syntheticNamedExports === 'boolean') {
+ module.syntheticNamedExports = sourceDescription.syntheticNamedExports;
+ }
return transform(this.graph, sourceDescription, module);
})
.then((source: TransformModuleJSON | ModuleJSON) => {
@@ -370,11 +381,17 @@ export class ModuleLoader {
}
return Promise.resolve(externalModule);
} else {
- return this.fetchModule(resolvedId.id, importer, resolvedId.moduleSideEffects, false);
+ return this.fetchModule(
+ resolvedId.id,
+ importer,
+ resolvedId.moduleSideEffects,
+ resolvedId.syntheticNamedExports,
+ false
+ );
}
}
- private handleMissingImports(
+ private handleResolveId(
resolvedId: ResolvedId | null,
source: string,
importer: string
@@ -387,8 +404,13 @@ export class ModuleLoader {
return {
external: true,
id: source,
- moduleSideEffects: this.hasModuleSideEffects(source, true)
+ moduleSideEffects: this.hasModuleSideEffects(source, true),
+ syntheticNamedExports: false
};
+ } else {
+ if (resolvedId.external && resolvedId.syntheticNamedExports) {
+ this.graph.warn(errExternalSyntheticExports(source, importer));
+ }
}
return resolvedId;
}
@@ -407,7 +429,7 @@ export class ModuleLoader {
: resolveIdResult;
if (typeof id === 'string') {
- return this.fetchModule(id, undefined as any, true, isEntry);
+ return this.fetchModule(id, undefined as any, true, false, isEntry);
}
return error(errUnresolvedEntry(unresolvedId));
});
@@ -420,6 +442,7 @@ export class ModuleLoader {
let id = '';
let external = false;
let moduleSideEffects = null;
+ let syntheticNamedExports = false;
if (resolveIdResult) {
if (typeof resolveIdResult === 'object') {
id = resolveIdResult.id;
@@ -429,6 +452,9 @@ export class ModuleLoader {
if (typeof resolveIdResult.moduleSideEffects === 'boolean') {
moduleSideEffects = resolveIdResult.moduleSideEffects;
}
+ if (typeof resolveIdResult.syntheticNamedExports === 'boolean') {
+ syntheticNamedExports = resolveIdResult.syntheticNamedExports;
+ }
} else {
if (this.isExternal(resolveIdResult, importer, true)) {
external = true;
@@ -448,7 +474,8 @@ export class ModuleLoader {
moduleSideEffects:
typeof moduleSideEffects === 'boolean'
? moduleSideEffects
- : this.hasModuleSideEffects(id, external)
+ : this.hasModuleSideEffects(id, external),
+ syntheticNamedExports
};
}
@@ -478,13 +505,9 @@ export class ModuleLoader {
if (resolution == null) {
return (module.resolvedIds[specifier] =
module.resolvedIds[specifier] ||
- this.handleMissingImports(
- await this.resolveId(specifier, module.id),
- specifier,
- module.id
- ));
+ this.handleResolveId(await this.resolveId(specifier, module.id), specifier, module.id));
}
- return this.handleMissingImports(
+ return this.handleResolveId(
this.normalizeResolveIdResult(resolution, importer, specifier),
specifier,
importer
diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts
index b72eee28c16..46b0f49138f 100644
--- a/src/ast/variables/NamespaceVariable.ts
+++ b/src/ast/variables/NamespaceVariable.ts
@@ -15,11 +15,13 @@ export default class NamespaceVariable extends Variable {
private containsExternalNamespace = false;
private referencedEarly = false;
private references: Identifier[] = [];
+ private syntheticNamedExports: boolean;
- constructor(context: AstContext) {
+ constructor(context: AstContext, syntheticNamedExports: boolean) {
super(context.getModuleName());
this.context = context;
this.module = context.module;
+ this.syntheticNamedExports = syntheticNamedExports;
}
addReference(identifier: Identifier) {
@@ -99,10 +101,15 @@ export default class NamespaceVariable extends Variable {
}
const name = this.getName();
+ let output = `{${n}${members.join(`,${n}`)}${n}}`;
+ if (this.syntheticNamedExports) {
+ output = `/*#__PURE__*/Object.assign(${output}, ${this.module.getDefaultExport().getName()})`;
+ }
+ if (options.freeze) {
+ output = `/*#__PURE__*/Object.freeze(${output})`;
+ }
- const callee = options.freeze ? `/*#__PURE__*/Object.freeze` : '';
- const membersStr = members.join(`,${n}`);
- let output = `${options.varOrConst} ${name}${_}=${_}${callee}({${n}${membersStr}${n}});`;
+ output = `${options.varOrConst} ${name}${_}=${_}${output};`;
if (options.format === 'system' && this.exportName) {
output += `${n}exports('${this.exportName}',${_}${name});`;
diff --git a/src/ast/variables/SyntheticNamedExportVariable.ts b/src/ast/variables/SyntheticNamedExportVariable.ts
new file mode 100644
index 00000000000..da07fd189d1
--- /dev/null
+++ b/src/ast/variables/SyntheticNamedExportVariable.ts
@@ -0,0 +1,25 @@
+import Module, { AstContext } from '../../Module';
+import { InclusionContext } from '../ExecutionContext';
+import ExportDefaultVariable from './ExportDefaultVariable';
+import Variable from './Variable';
+
+export default class SyntheticNamedExportVariableVariable extends Variable {
+ context: AstContext;
+ defaultVariable: ExportDefaultVariable;
+ module: Module;
+
+ constructor(context: AstContext, name: string, defaultVariable: ExportDefaultVariable) {
+ super(name);
+ this.context = context;
+ this.module = context.module;
+ this.defaultVariable = defaultVariable;
+ this.setRenderNames(defaultVariable.getName(), name);
+ }
+
+ include(context: InclusionContext) {
+ if (!this.included) {
+ this.included = true;
+ this.context.includeVariable(context, this.defaultVariable);
+ }
+ }
+}
diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts
index 323059f48f3..187eb553e82 100644
--- a/src/ast/variables/Variable.ts
+++ b/src/ast/variables/Variable.ts
@@ -49,7 +49,7 @@ export default class Variable implements ExpressionEntity {
getName(): string {
const name = this.renderName || this.name;
- return this.renderBaseName ? `${this.renderBaseName}.${name}` : name;
+ return this.renderBaseName ? `${this.renderBaseName}${getPropertyAccess(name)}` : name;
}
getReturnExpressionWhenCalledAtPath(
@@ -107,3 +107,7 @@ export default class Variable implements ExpressionEntity {
return this.name;
}
}
+
+const getPropertyAccess = (name: string) => {
+ return /^(?!\d)[\w$]+$/.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
+};
diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts
index 6c8397dee79..3e5b7859e5f 100644
--- a/src/rollup/types.d.ts
+++ b/src/rollup/types.d.ts
@@ -93,6 +93,7 @@ export interface SourceDescription {
code: string;
map?: SourceMapInput;
moduleSideEffects?: boolean | null;
+ syntheticNamedExports?: boolean;
}
export interface TransformSourceDescription extends SourceDescription {
@@ -109,6 +110,7 @@ export interface TransformModuleJSON {
originalSourcemap: ExistingDecodedSourceMap | null;
resolvedIds?: ResolvedIdMap;
sourcemapChain: DecodedSourceMapOrMissing[];
+ syntheticNamedExports: boolean | null;
transformDependencies: string[];
}
@@ -199,6 +201,7 @@ export interface ResolvedId {
external: boolean;
id: string;
moduleSideEffects: boolean;
+ syntheticNamedExports: boolean;
}
export interface ResolvedIdMap {
@@ -209,6 +212,7 @@ interface PartialResolvedId {
external?: boolean;
id: string;
moduleSideEffects?: boolean | null;
+ syntheticNamedExports?: boolean;
}
export type ResolveIdResult = string | false | null | undefined | PartialResolvedId;
diff --git a/src/utils/error.ts b/src/utils/error.ts
index 9adad000a88..3bacdd45afb 100644
--- a/src/utils/error.ts
+++ b/src/utils/error.ts
@@ -54,7 +54,9 @@ export enum Errors {
PLUGIN_ERROR = 'PLUGIN_ERROR',
UNRESOLVED_ENTRY = 'UNRESOLVED_ENTRY',
UNRESOLVED_IMPORT = 'UNRESOLVED_IMPORT',
- VALIDATION_ERROR = 'VALIDATION_ERROR'
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
+ EXTERNAL_SYNTHETIC_EXPORTS = 'EXTERNAL_SYNTHETIC_EXPORTS',
+ SYNTHETIC_NAMED_EXPORTS_NEED_DEFAULT = 'SYNTHETIC_NAMED_EXPORTS_NEED_DEFAULT'
}
export function errAssetNotFinalisedForFileName(name: string) {
@@ -263,6 +265,15 @@ export function errUnresolvedImportTreatedAsExternal(source: string, importer: s
};
}
+export function errExternalSyntheticExports(source: string, importer: string) {
+ return {
+ code: Errors.EXTERNAL_SYNTHETIC_EXPORTS,
+ importer: relativeId(importer),
+ message: `External '${source}' can not have 'syntheticNamedExports' enabled.`,
+ source
+ };
+}
+
export function errFailedValidation(message: string) {
return {
code: Errors.VALIDATION_ERROR,
diff --git a/src/utils/transform.ts b/src/utils/transform.ts
index 9c83aa32f45..4f27bfcd1ad 100644
--- a/src/utils/transform.ts
+++ b/src/utils/transform.ts
@@ -35,6 +35,7 @@ export default function transform(
const emittedFiles: EmittedFile[] = [];
let customTransformCache = false;
let moduleSideEffects: boolean | null = null;
+ let syntheticNamedExports: boolean | null = null;
let trackedPluginCache: { cache: PluginCache; used: boolean };
let curPlugin: Plugin;
const curSource: string = source.code;
@@ -82,6 +83,9 @@ export default function transform(
if (typeof result.moduleSideEffects === 'boolean') {
moduleSideEffects = result.moduleSideEffects;
}
+ if (typeof result.syntheticNamedExports === 'boolean') {
+ syntheticNamedExports = result.syntheticNamedExports;
+ }
} else {
return code;
}
@@ -193,6 +197,7 @@ export default function transform(
originalCode,
originalSourcemap,
sourcemapChain,
+ syntheticNamedExports,
transformDependencies
};
});
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/_config.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_config.js
new file mode 100644
index 00000000000..2bc10bd7bed
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_config.js
@@ -0,0 +1,20 @@
+module.exports = {
+ description: 'mixed synthetic named exports',
+ options: {
+ input: ['main.js'],
+ plugins: [
+ {
+ transform(code, id) {
+ console.log(id);
+ if (id.endsWith('/dep1.js') || id.endsWith('/dep2.js')) {
+ return {
+ code,
+ syntheticNamedExports: true
+ };
+ }
+ return null;
+ }
+ }
+ ]
+ }
+};
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/amd/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/amd/main.js
new file mode 100644
index 00000000000..2d5a6a148b1
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/amd/main.js
@@ -0,0 +1,7 @@
+define(function () { 'use strict';
+
+ var dep2 = {bar: {foo: 'works'}};
+
+ console.log(dep2.bar.foo);
+
+});
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/cjs/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/cjs/main.js
new file mode 100644
index 00000000000..3a897a26f6d
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/cjs/main.js
@@ -0,0 +1,5 @@
+'use strict';
+
+var dep2 = {bar: {foo: 'works'}};
+
+console.log(dep2.bar.foo);
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/es/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/es/main.js
new file mode 100644
index 00000000000..f84e5168475
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/es/main.js
@@ -0,0 +1,3 @@
+var dep2 = {bar: {foo: 'works'}};
+
+console.log(dep2.bar.foo);
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/system/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/system/main.js
new file mode 100644
index 00000000000..0efe9ac0943
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/_expected/system/main.js
@@ -0,0 +1,12 @@
+System.register([], function () {
+ 'use strict';
+ return {
+ execute: function () {
+
+ var dep2 = {bar: {foo: 'works'}};
+
+ console.log(dep2.bar.foo);
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep1.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep1.js
new file mode 100644
index 00000000000..4561eb9a005
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep1.js
@@ -0,0 +1 @@
+export {bar as default} from './dep2.js';
\ No newline at end of file
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep2.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep2.js
new file mode 100644
index 00000000000..8f16601da67
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/dep2.js
@@ -0,0 +1 @@
+export default {bar: {foo: 'works'}};
\ No newline at end of file
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports-2/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports-2/main.js
new file mode 100644
index 00000000000..5a747a56c3b
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports-2/main.js
@@ -0,0 +1,2 @@
+import { foo } from './dep1.js';
+console.log(foo);
\ No newline at end of file
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/_config.js b/test/chunking-form/samples/mixed-synthetic-named-exports/_config.js
new file mode 100644
index 00000000000..eb273960237
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/_config.js
@@ -0,0 +1,19 @@
+module.exports = {
+ description: 'mixed synthetic named exports',
+ options: {
+ input: ['main.js'],
+ plugins: [
+ {
+ resolveId(id) {
+ if (id === './dep1.js') {
+ return {
+ id,
+ syntheticNamedExports: true
+ };
+ }
+ return null;
+ }
+ }
+ ]
+ }
+};
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/amd/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/amd/main.js
new file mode 100644
index 00000000000..6285d62559e
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/amd/main.js
@@ -0,0 +1,19 @@
+define(function () { 'use strict';
+
+ const d = {
+ fn: 42,
+ hello: 'hola'
+ };
+ const foo = 100;
+
+ var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign({
+ __proto__: null,
+ foo: foo,
+ 'default': d
+ }, d));
+
+ console.log(d.fn);
+ console.log(foo);
+ console.log(ns);
+
+});
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/cjs/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/cjs/main.js
new file mode 100644
index 00000000000..f2c1b375abb
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/cjs/main.js
@@ -0,0 +1,17 @@
+'use strict';
+
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+const foo = 100;
+
+var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign({
+ __proto__: null,
+ foo: foo,
+ 'default': d
+}, d));
+
+console.log(d.fn);
+console.log(foo);
+console.log(ns);
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/es/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/es/main.js
new file mode 100644
index 00000000000..3b23f6d33bb
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/es/main.js
@@ -0,0 +1,15 @@
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+const foo = 100;
+
+var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign({
+ __proto__: null,
+ foo: foo,
+ 'default': d
+}, d));
+
+console.log(d.fn);
+console.log(foo);
+console.log(ns);
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/system/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/system/main.js
new file mode 100644
index 00000000000..1147e48c999
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/_expected/system/main.js
@@ -0,0 +1,24 @@
+System.register([], function () {
+ 'use strict';
+ return {
+ execute: function () {
+
+ const d = {
+ fn: 42,
+ hello: 'hola'
+ };
+ const foo = 100;
+
+ var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.assign({
+ __proto__: null,
+ foo: foo,
+ 'default': d
+ }, d));
+
+ console.log(d.fn);
+ console.log(foo);
+ console.log(ns);
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/dep1.js b/test/chunking-form/samples/mixed-synthetic-named-exports/dep1.js
new file mode 100644
index 00000000000..2f0388e7a09
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/dep1.js
@@ -0,0 +1,6 @@
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+export const foo = 100;
+export default d;
\ No newline at end of file
diff --git a/test/chunking-form/samples/mixed-synthetic-named-exports/main.js b/test/chunking-form/samples/mixed-synthetic-named-exports/main.js
new file mode 100644
index 00000000000..9f9f926d5ad
--- /dev/null
+++ b/test/chunking-form/samples/mixed-synthetic-named-exports/main.js
@@ -0,0 +1,6 @@
+import {fn, foo} from './dep1.js';
+import * as ns from './dep1.js';
+
+console.log(fn);
+console.log(foo);
+console.log(ns);
\ No newline at end of file
diff --git a/test/chunking-form/samples/synthetic-named-exports/_config.js b/test/chunking-form/samples/synthetic-named-exports/_config.js
new file mode 100644
index 00000000000..9327ae3aa2c
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/_config.js
@@ -0,0 +1,30 @@
+module.exports = {
+ description: 'synthetic named exports',
+ options: {
+ input: ['main.js'],
+ plugins: [
+ {
+ resolveId(id) {
+ if (id === './dep1.js') {
+ return id;
+ }
+ return null;
+ },
+ load(id) {
+ if (id === './dep1.js') {
+ return {
+ code: `
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+export default d;`,
+ syntheticNamedExports: true
+ };
+ }
+ return null;
+ }
+ }
+ ]
+ }
+};
diff --git a/test/chunking-form/samples/synthetic-named-exports/_expected/amd/main.js b/test/chunking-form/samples/synthetic-named-exports/_expected/amd/main.js
new file mode 100644
index 00000000000..569d66e02bb
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/_expected/amd/main.js
@@ -0,0 +1,12 @@
+define(function () { 'use strict';
+
+ const d = {
+ fn: 42,
+ hello: 'hola'
+ };
+
+ console.log(d.fn);
+ console.log(d.foo);
+ console.log(d["some-prop"]);
+
+});
diff --git a/test/chunking-form/samples/synthetic-named-exports/_expected/cjs/main.js b/test/chunking-form/samples/synthetic-named-exports/_expected/cjs/main.js
new file mode 100644
index 00000000000..cd2f4f43d7b
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/_expected/cjs/main.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+
+console.log(d.fn);
+console.log(d.foo);
+console.log(d["some-prop"]);
diff --git a/test/chunking-form/samples/synthetic-named-exports/_expected/es/main.js b/test/chunking-form/samples/synthetic-named-exports/_expected/es/main.js
new file mode 100644
index 00000000000..ecd2e3e91a7
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/_expected/es/main.js
@@ -0,0 +1,8 @@
+const d = {
+ fn: 42,
+ hello: 'hola'
+};
+
+console.log(d.fn);
+console.log(d.foo);
+console.log(d["some-prop"]);
diff --git a/test/chunking-form/samples/synthetic-named-exports/_expected/system/main.js b/test/chunking-form/samples/synthetic-named-exports/_expected/system/main.js
new file mode 100644
index 00000000000..6b161eef8ae
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/_expected/system/main.js
@@ -0,0 +1,17 @@
+System.register([], function () {
+ 'use strict';
+ return {
+ execute: function () {
+
+ const d = {
+ fn: 42,
+ hello: 'hola'
+ };
+
+ console.log(d.fn);
+ console.log(d.foo);
+ console.log(d["some-prop"]);
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/synthetic-named-exports/main.js b/test/chunking-form/samples/synthetic-named-exports/main.js
new file mode 100644
index 00000000000..5b675b95c0f
--- /dev/null
+++ b/test/chunking-form/samples/synthetic-named-exports/main.js
@@ -0,0 +1,6 @@
+import {fn, foo} from './dep1.js';
+import * as ns from './dep1.js';
+
+console.log(fn);
+console.log(foo);
+console.log(ns['some-prop']);
\ No newline at end of file
diff --git a/test/form/samples/freeze/_expected/amd.js b/test/form/samples/freeze/_expected/amd.js
index 60c6a2683a6..6d5f5acc935 100644
--- a/test/form/samples/freeze/_expected/amd.js
+++ b/test/form/samples/freeze/_expected/amd.js
@@ -3,11 +3,11 @@ define(['exports'], function (exports) { 'use strict';
const foo = 1;
const bar = 2;
- var namespace = ({
+ var namespace = {
__proto__: null,
foo: foo,
bar: bar
- });
+ };
console.log( Object.keys( namespace ) );
diff --git a/test/form/samples/freeze/_expected/cjs.js b/test/form/samples/freeze/_expected/cjs.js
index c010fbc614a..2ee4ef34da1 100644
--- a/test/form/samples/freeze/_expected/cjs.js
+++ b/test/form/samples/freeze/_expected/cjs.js
@@ -5,11 +5,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
const foo = 1;
const bar = 2;
-var namespace = ({
+var namespace = {
__proto__: null,
foo: foo,
bar: bar
-});
+};
console.log( Object.keys( namespace ) );
diff --git a/test/form/samples/freeze/_expected/es.js b/test/form/samples/freeze/_expected/es.js
index 45de6b68c63..1285f913af8 100644
--- a/test/form/samples/freeze/_expected/es.js
+++ b/test/form/samples/freeze/_expected/es.js
@@ -1,11 +1,11 @@
const foo = 1;
const bar = 2;
-var namespace = ({
+var namespace = {
__proto__: null,
foo: foo,
bar: bar
-});
+};
console.log( Object.keys( namespace ) );
diff --git a/test/form/samples/freeze/_expected/iife.js b/test/form/samples/freeze/_expected/iife.js
index 1dc95266499..f42a252dfcb 100644
--- a/test/form/samples/freeze/_expected/iife.js
+++ b/test/form/samples/freeze/_expected/iife.js
@@ -4,11 +4,11 @@ var myBundle = (function (exports) {
const foo = 1;
const bar = 2;
- var namespace = ({
+ var namespace = {
__proto__: null,
foo: foo,
bar: bar
- });
+ };
console.log( Object.keys( namespace ) );
diff --git a/test/form/samples/freeze/_expected/system.js b/test/form/samples/freeze/_expected/system.js
index c331d83ceac..61af5d66116 100644
--- a/test/form/samples/freeze/_expected/system.js
+++ b/test/form/samples/freeze/_expected/system.js
@@ -6,11 +6,11 @@ System.register('myBundle', [], function (exports) {
const foo = 1;
const bar = 2;
- var namespace = ({
+ var namespace = {
__proto__: null,
foo: foo,
bar: bar
- });
+ };
console.log( Object.keys( namespace ) );
diff --git a/test/form/samples/freeze/_expected/umd.js b/test/form/samples/freeze/_expected/umd.js
index 10fb997b6a2..1d2f030b1f6 100644
--- a/test/form/samples/freeze/_expected/umd.js
+++ b/test/form/samples/freeze/_expected/umd.js
@@ -7,11 +7,11 @@
const foo = 1;
const bar = 2;
- var namespace = ({
+ var namespace = {
__proto__: null,
foo: foo,
bar: bar
- });
+ };
console.log( Object.keys( namespace ) );
diff --git a/test/function/samples/context-resolve/_config.js b/test/function/samples/context-resolve/_config.js
index 88aed16c12a..c269e5be58d 100644
--- a/test/function/samples/context-resolve/_config.js
+++ b/test/function/samples/context-resolve/_config.js
@@ -7,7 +7,8 @@ const tests = [
expected: {
id: path.resolve(__dirname, 'existing.js'),
external: false,
- moduleSideEffects: true
+ moduleSideEffects: true,
+ syntheticNamedExports: false
}
},
{
@@ -23,7 +24,8 @@ const tests = [
expected: {
id: path.resolve(__dirname, 'marked-directly-external-relative'),
external: true,
- moduleSideEffects: true
+ moduleSideEffects: true,
+ syntheticNamedExports: false
}
},
{
@@ -31,36 +33,63 @@ const tests = [
expected: {
id: path.resolve(__dirname, 'marked-external-relative'),
external: true,
- moduleSideEffects: true
+ moduleSideEffects: true,
+ syntheticNamedExports: false
}
},
{
source: 'marked-external-absolute',
- expected: { id: 'marked-external-absolute', external: true, moduleSideEffects: true }
+ expected: {
+ id: 'marked-external-absolute',
+ external: true,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
},
{
source: 'resolved-name',
- expected: { id: 'resolved:resolved-name', external: false, moduleSideEffects: true }
+ expected: {
+ id: 'resolved:resolved-name',
+ external: false,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
},
{
source: 'resolved-false',
- expected: { id: 'resolved-false', external: true, moduleSideEffects: true }
+ expected: {
+ id: 'resolved-false',
+ external: true,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
},
{
source: 'resolved-object',
- expected: { id: 'resolved:resolved-object', external: false, moduleSideEffects: true }
+ expected: {
+ id: 'resolved:resolved-object',
+ external: false,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
},
{
source: 'resolved-object-non-external',
expected: {
id: 'resolved:resolved-object-non-external',
external: false,
- moduleSideEffects: true
+ moduleSideEffects: true,
+ syntheticNamedExports: false
}
},
{
source: 'resolved-object-external',
- expected: { id: 'resolved:resolved-object-external', external: true, moduleSideEffects: true }
+ expected: {
+ id: 'resolved:resolved-object-external',
+ external: true,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
}
];
diff --git a/test/function/samples/external-synthetic-exports/_config.js b/test/function/samples/external-synthetic-exports/_config.js
new file mode 100644
index 00000000000..bd60de83d00
--- /dev/null
+++ b/test/function/samples/external-synthetic-exports/_config.js
@@ -0,0 +1,31 @@
+module.exports = {
+ description: 'external modules can not have syntheticNamedExports',
+ options: {
+ plugins: [
+ {
+ resolveId(id) {
+ if (id === 'dep') {
+ return {
+ id,
+ external: true,
+ syntheticNamedExports: true
+ };
+ }
+ }
+ }
+ ]
+ },
+ warnings: [
+ {
+ code: 'EXTERNAL_SYNTHETIC_EXPORTS',
+ importer: 'main.js',
+ source: 'dep',
+ message: "External 'dep' can not have 'syntheticNamedExports' enabled."
+ }
+ ],
+ context: {
+ require() {
+ return 1;
+ }
+ }
+};
diff --git a/test/function/samples/external-synthetic-exports/main.js b/test/function/samples/external-synthetic-exports/main.js
new file mode 100644
index 00000000000..7ff5eca2612
--- /dev/null
+++ b/test/function/samples/external-synthetic-exports/main.js
@@ -0,0 +1,2 @@
+import { foo } from 'dep';
+foo;
diff --git a/test/function/samples/synthetic-exports-need-default/_config.js b/test/function/samples/synthetic-exports-need-default/_config.js
new file mode 100644
index 00000000000..7e77cd9721d
--- /dev/null
+++ b/test/function/samples/synthetic-exports-need-default/_config.js
@@ -0,0 +1,25 @@
+const path = require('path');
+
+module.exports = {
+ description: 'synthetic named exports moduleds need a default export',
+ options: {
+ plugins: [
+ {
+ resolveId(id) {
+ if (id === './dep.js') {
+ return {
+ id,
+ syntheticNamedExports: true
+ };
+ }
+ }
+ }
+ ]
+ },
+ error: {
+ code: 'SYNTHETIC_NAMED_EXPORTS_NEED_DEFAULT',
+ id: './dep.js',
+ message: "Modules with 'syntheticNamedExports' need a default export.",
+ watchFiles: [path.resolve(__dirname, 'main.js'), './dep.js']
+ }
+};
diff --git a/test/function/samples/synthetic-exports-need-default/dep.js b/test/function/samples/synthetic-exports-need-default/dep.js
new file mode 100644
index 00000000000..6a8018af412
--- /dev/null
+++ b/test/function/samples/synthetic-exports-need-default/dep.js
@@ -0,0 +1 @@
+export const foo = 1;
\ No newline at end of file
diff --git a/test/function/samples/synthetic-exports-need-default/main.js b/test/function/samples/synthetic-exports-need-default/main.js
new file mode 100644
index 00000000000..a5e73b0d555
--- /dev/null
+++ b/test/function/samples/synthetic-exports-need-default/main.js
@@ -0,0 +1,3 @@
+import { a } from './dep.js';
+a;
+console.log(a);
diff --git a/test/incremental/index.js b/test/incremental/index.js
index 147ba0789de..28041c41562 100644
--- a/test/incremental/index.js
+++ b/test/incremental/index.js
@@ -265,8 +265,18 @@ describe('incremental', () => {
assert.equal(bundle.cache.modules[1].id, 'entry');
assert.deepEqual(bundle.cache.modules[1].resolvedIds, {
- foo: { id: 'foo', external: false, moduleSideEffects: true },
- external: { id: 'external', external: true, moduleSideEffects: true }
+ foo: {
+ id: 'foo',
+ external: false,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ },
+ external: {
+ id: 'external',
+ external: true,
+ moduleSideEffects: true,
+ syntheticNamedExports: false
+ }
});
});
});