From fc29fa649674ffc0d05c5947b6325aa5f12778b4 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 20 Mar 2020 06:52:43 +0100 Subject: [PATCH] Support custom rendering for dynamic imports --- src/Chunk.ts | 19 ++++++---- src/ModuleLoader.ts | 2 +- src/ast/nodes/ImportExpression.ts | 31 ++++++++++------ src/ast/nodes/MetaProperty.ts | 3 +- src/rollup/rollup.ts | 2 +- src/rollup/types.d.ts | 35 +++++++++---------- src/utils/renderHelpers.ts | 5 ++- src/utils/{defaultPlugin.ts => resolveId.ts} | 1 - .../samples/render-dynamic-import/_config.js | 22 ++++++++++++ .../generated-imported-via-special-handler.js | 5 +++ .../_expected/amd/main.js | 26 ++++++++++++++ .../generated-imported-via-special-handler.js | 3 ++ .../_expected/cjs/main.js | 24 +++++++++++++ .../generated-imported-via-special-handler.js | 1 + .../_expected/es/main.js | 3 ++ .../generated-imported-via-special-handler.js | 10 ++++++ .../_expected/system/main.js | 12 +++++++ .../imported-via-special-handler.js | 1 + .../samples/render-dynamic-import/main.js | 3 ++ 19 files changed, 166 insertions(+), 42 deletions(-) rename src/utils/{defaultPlugin.ts => resolveId.ts} (98%) create mode 100644 test/chunking-form/samples/render-dynamic-import/_config.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/amd/generated-imported-via-special-handler.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/amd/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/cjs/generated-imported-via-special-handler.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/cjs/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/es/generated-imported-via-special-handler.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/es/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/system/generated-imported-via-special-handler.js create mode 100644 test/chunking-form/samples/render-dynamic-import/_expected/system/main.js create mode 100644 test/chunking-form/samples/render-dynamic-import/imported-via-special-handler.js create mode 100644 test/chunking-form/samples/render-dynamic-import/main.js diff --git a/src/Chunk.ts b/src/Chunk.ts index 8f68155b688..d829d6ba0d9 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -16,6 +16,7 @@ import Module from './Module'; import { DecodedSourceMapOrMissing, GlobalsOption, + InternalModuleFormat, OutputOptions, PreRenderedChunk, RenderedChunk, @@ -411,7 +412,7 @@ export default class Chunk { } // prerender allows chunk hashes and names to be generated before finalizing - preRender(options: OutputOptions, inputBase: string) { + preRender(options: OutputOptions, inputBase: string, outputPluginDriver: PluginDriver) { timeStart('render modules', 3); const magicString = new MagicStringBundle({ separator: options.compact ? '' : '\n\n' }); @@ -424,10 +425,11 @@ export default class Chunk { const renderOptions: RenderOptions = { compact: options.compact as boolean, dynamicImportFunction: options.dynamicImportFunction as string, - format: options.format as string, + format: options.format as InternalModuleFormat, freeze: options.freeze !== false, indent: this.indentString, namespaceToStringTag: options.namespaceToStringTag === true, + outputPluginDriver, varOrConst: options.preferConst ? 'const' : 'var' }; @@ -520,7 +522,7 @@ export default class Chunk { timeStart('render format', 3); const chunkId = this.id!; - const format = options.format as string; + const format = options.format as InternalModuleFormat; const finalise = finalisers[format]; if (options.dynamicImportFunction && format !== 'es') { this.graph.warn({ @@ -720,7 +722,10 @@ export default class Chunk { } } - private finaliseImportMetas(format: string, outputPluginDriver: PluginDriver): void { + private finaliseImportMetas( + format: InternalModuleFormat, + outputPluginDriver: PluginDriver + ): void { for (const [module, code] of this.renderedModuleSources) { for (const importMeta of module.importMetas) { importMeta.renderFinalMechanism(code, this.id!, format, outputPluginDriver); @@ -923,12 +928,12 @@ export default class Chunk { if (resolution instanceof Module) { if (resolution.chunk === this) { const namespace = resolution.getOrCreateNamespace(); - node.setResolution('named', namespace); + node.setResolution('named', resolution, namespace); } else { - node.setResolution(resolution.chunk!.exportMode); + node.setResolution(resolution.chunk!.exportMode, resolution); } } else { - node.setResolution('auto'); + node.setResolution('auto', resolution); } } } diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index be384eceb11..8415b17c78e 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -13,7 +13,6 @@ import { ResolveIdResult, TransformModuleJSON } from './rollup/types'; -import { resolveId } from './utils/defaultPlugin'; import { errBadLoader, errCannotAssignModuleToChunk, @@ -31,6 +30,7 @@ import { readFile } from './utils/fs'; import { isRelative, resolve } from './utils/path'; import { PluginDriver } from './utils/PluginDriver'; import relativeId from './utils/relativeId'; +import { resolveId } from './utils/resolveId'; import { timeEnd, timeStart } from './utils/timers'; import transform from './utils/transform'; diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index a30da21c771..be4a895f41c 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -1,4 +1,6 @@ import MagicString from 'magic-string'; +import ExternalModule from '../../ExternalModule'; +import Module from '../../Module'; import { findFirstOccurrenceOutsideComment, RenderOptions } from '../../utils/renderHelpers'; import { INTEROP_NAMESPACE_VARIABLE } from '../../utils/variableNames'; import { InclusionContext } from '../ExecutionContext'; @@ -12,11 +14,12 @@ interface DynamicImportMechanism { } export default class Import extends NodeBase { - inlineNamespace?: NamespaceVariable; + inlineNamespace: NamespaceVariable | null = null; source!: ExpressionNode; type!: NodeType.tImportExpression; private exportMode: 'none' | 'named' | 'default' | 'auto' = 'auto'; + private resolution: Module | ExternalModule | string | null = null; hasEffects(): boolean { return true; @@ -47,7 +50,6 @@ export default class Import extends NodeBase { return; } - // TODO Lukas get the import mechanism via the hook const importMechanism = this.getDynamicImportMechanism(options); if (importMechanism) { code.overwrite( @@ -66,9 +68,11 @@ export default class Import extends NodeBase { setResolution( exportMode: 'none' | 'named' | 'default' | 'auto', - inlineNamespace?: NamespaceVariable + resolution: Module | ExternalModule | string | null, + inlineNamespace: NamespaceVariable | false = false ): void { this.exportMode = exportMode; + this.resolution = resolution; if (inlineNamespace) { this.inlineNamespace = inlineNamespace; } else { @@ -86,14 +90,19 @@ export default class Import extends NodeBase { } } - private getDynamicImportMechanism( - options: RenderOptions - // pluginDriver: PluginDriver - ): DynamicImportMechanism | null { - // const mechanism = pluginDriver.hookFirstSync('renderDynamicImport', [{}]); - // if (mechanism) { - // return mechanism; - // } + private getDynamicImportMechanism(options: RenderOptions): DynamicImportMechanism | null { + const mechanism = options.outputPluginDriver.hookFirstSync('renderDynamicImport', [ + { + customResolution: typeof this.resolution === 'string' ? this.resolution : null, + format: options.format, + moduleId: this.context.module.id, + targetModuleId: + this.resolution && typeof this.resolution !== 'string' ? this.resolution.id : null + } + ]); + if (mechanism) { + return mechanism; + } switch (options.format) { case 'cjs': { const _ = options.compact ? '' : ' '; diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index a6db093b5dd..ff169fae919 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -1,4 +1,5 @@ import MagicString from 'magic-string'; +import { InternalModuleFormat } from '../../rollup/types'; import { dirname, normalize, relative } from '../../utils/path'; import { PluginDriver } from '../../utils/PluginDriver'; import { ObjectPathKey } from '../utils/PathTracker'; @@ -56,7 +57,7 @@ export default class MetaProperty extends NodeBase { renderFinalMechanism( code: MagicString, chunkId: string, - format: string, + format: InternalModuleFormat, outputPluginDriver: PluginDriver ): void { if (!this.included) return; diff --git a/src/rollup/rollup.ts b/src/rollup/rollup.ts index f60ddae3ed2..917a8f5d5dd 100644 --- a/src/rollup/rollup.ts +++ b/src/rollup/rollup.ts @@ -212,7 +212,7 @@ export async function rollupInternal( chunk.exportMode = getExportMode(chunk, outputOptions, chunk.facadeModule!.id); } for (const chunk of chunks) { - chunk.preRender(outputOptions, inputBase); + chunk.preRender(outputOptions, inputBase, outputPluginDriver); } assignChunkIds( chunks, diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 28199d93fbe..712affcb83a 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -261,7 +261,7 @@ export type ResolveDynamicImportHook = ( export type ResolveImportMetaHook = ( this: PluginContext, prop: string | null, - options: { chunkId: string; format: string; moduleId: string } + options: { chunkId: string; format: InternalModuleFormat; moduleId: string } ) => string | null | undefined; export type ResolveAssetUrlHook = ( @@ -269,7 +269,7 @@ export type ResolveAssetUrlHook = ( options: { assetFileName: string; chunkId: string; - format: string; + format: InternalModuleFormat; moduleId: string; relativeAssetPath: string; } @@ -282,7 +282,7 @@ export type ResolveFileUrlHook = ( chunkId: string; chunkReferenceId: string | null; fileName: string; - format: string; + format: InternalModuleFormat; moduleId: string; referenceId: string; relativePath: string; @@ -337,10 +337,15 @@ interface OutputPluginHooks { ) => void | Promise; outputOptions: (this: PluginContext, options: OutputOptions) => OutputOptions | null | undefined; renderChunk: RenderChunkHook; - // renderDynamicImport: ( - // this: PluginContext, - // options: {} - // ) => { left: string; right: string } | null | undefined; + renderDynamicImport: ( + this: PluginContext, + options: { + customResolution: string | null; + format: InternalModuleFormat; + moduleId: string; + targetModuleId: string | null; + } + ) => { left: string; right: string } | null | undefined; renderError: (this: PluginContext, err?: Error) => Promise | void; renderStart: ( this: PluginContext, @@ -377,7 +382,7 @@ export type SyncPluginHooks = Exclude; export type FirstPluginHooks = | 'load' - // | 'renderDynamicImport' + | 'renderDynamicImport' | 'resolveAssetUrl' | 'resolveDynamicImport' | 'resolveFileUrl' @@ -461,17 +466,9 @@ export interface InputOptions { watch?: WatcherOptions; } -export type ModuleFormat = - | 'amd' - | 'cjs' - | 'commonjs' - | 'es' - | 'esm' - | 'iife' - | 'module' - | 'system' - | 'systemjs' - | 'umd'; +export type InternalModuleFormat = 'amd' | 'cjs' | 'es' | 'iife' | 'system' | 'umd'; + +export type ModuleFormat = InternalModuleFormat | 'commonjs' | 'esm' | 'module' | 'systemjs'; export type OptionsPaths = Record | ((id: string) => string); diff --git a/src/utils/renderHelpers.ts b/src/utils/renderHelpers.ts index 1363ce535e4..74a764a47d7 100644 --- a/src/utils/renderHelpers.ts +++ b/src/utils/renderHelpers.ts @@ -1,14 +1,17 @@ import MagicString from 'magic-string'; import { Node, StatementNode } from '../ast/nodes/shared/Node'; +import { InternalModuleFormat } from '../rollup/types'; +import { PluginDriver } from './PluginDriver'; import { treeshakeNode } from './treeshakeNode'; export interface RenderOptions { compact: boolean; dynamicImportFunction: string; - format: string; + format: InternalModuleFormat; freeze: boolean; indent: string; namespaceToStringTag: boolean; + outputPluginDriver: PluginDriver; varOrConst: 'var' | 'const'; } diff --git a/src/utils/defaultPlugin.ts b/src/utils/resolveId.ts similarity index 98% rename from src/utils/defaultPlugin.ts rename to src/utils/resolveId.ts index 5fc0664c6a4..57f5ee7abe6 100644 --- a/src/utils/defaultPlugin.ts +++ b/src/utils/resolveId.ts @@ -3,7 +3,6 @@ import { lstatSync, readdirSync, realpathSync } from './fs'; import { basename, dirname, isAbsolute, resolve } from './path'; import { PluginDriver } from './PluginDriver'; -// TODO Lukas rename file export async function resolveId( source: string, importer: string | undefined, diff --git a/test/chunking-form/samples/render-dynamic-import/_config.js b/test/chunking-form/samples/render-dynamic-import/_config.js new file mode 100644 index 00000000000..c841123e304 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_config.js @@ -0,0 +1,22 @@ +const path = require('path'); + +module.exports = { + description: 'supports custom rendering for dynamic imports', + options: { + plugins: { + name: 'test-plugin', + resolveDynamicImport(specifier) { + if (typeof specifier === 'object' && specifier.name === 'someResolvedVariable') { + return 'someCustomlyResolvedVariable'; + } + }, + renderDynamicImport({ customResolution, format, moduleId, targetModuleId }) { + return { + left: `${format}SpecialHandler(`, + right: `, '${path.relative(__dirname, moduleId)}', '${targetModuleId && + path.relative(__dirname, targetModuleId)}', ${customResolution})` + }; + } + } + } +}; diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/amd/generated-imported-via-special-handler.js b/test/chunking-form/samples/render-dynamic-import/_expected/amd/generated-imported-via-special-handler.js new file mode 100644 index 00000000000..96ce7e1d3f0 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/amd/generated-imported-via-special-handler.js @@ -0,0 +1,5 @@ +define(function () { 'use strict'; + + console.log('special'); + +}); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/amd/main.js b/test/chunking-form/samples/render-dynamic-import/_expected/amd/main.js new file mode 100644 index 00000000000..bf711f8dc59 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/amd/main.js @@ -0,0 +1,26 @@ +define(['require'], function (require) { 'use strict'; + + function _interopNamespace(e) { + if (e && e.__esModule) { return e; } else { + var n = {}; + if (e) { + Object.keys(e).forEach(function (k) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { + return e[k]; + } + }); + }); + } + n['default'] = e; + return n; + } + } + + amdSpecialHandler('./generated-imported-via-special-handler', 'main.js', 'imported-via-special-handler.js', null); + amdSpecialHandler(someVariable, 'main.js', 'null', null); + amdSpecialHandler(someCustomlyResolvedVariable, 'main.js', 'null', someCustomlyResolvedVariable); + +}); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/cjs/generated-imported-via-special-handler.js b/test/chunking-form/samples/render-dynamic-import/_expected/cjs/generated-imported-via-special-handler.js new file mode 100644 index 00000000000..2963ee147dd --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/cjs/generated-imported-via-special-handler.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('special'); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/cjs/main.js b/test/chunking-form/samples/render-dynamic-import/_expected/cjs/main.js new file mode 100644 index 00000000000..64e4c86028d --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/cjs/main.js @@ -0,0 +1,24 @@ +'use strict'; + +function _interopNamespace(e) { + if (e && e.__esModule) { return e; } else { + var n = {}; + if (e) { + Object.keys(e).forEach(function (k) { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { + return e[k]; + } + }); + }); + } + n['default'] = e; + return n; + } +} + +cjsSpecialHandler('./generated-imported-via-special-handler.js', 'main.js', 'imported-via-special-handler.js', null); +cjsSpecialHandler(someVariable, 'main.js', 'null', null); +cjsSpecialHandler(someCustomlyResolvedVariable, 'main.js', 'null', someCustomlyResolvedVariable); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/es/generated-imported-via-special-handler.js b/test/chunking-form/samples/render-dynamic-import/_expected/es/generated-imported-via-special-handler.js new file mode 100644 index 00000000000..6c8553ec696 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/es/generated-imported-via-special-handler.js @@ -0,0 +1 @@ +console.log('special'); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/es/main.js b/test/chunking-form/samples/render-dynamic-import/_expected/es/main.js new file mode 100644 index 00000000000..ddbe789b8b5 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/es/main.js @@ -0,0 +1,3 @@ +esSpecialHandler('./generated-imported-via-special-handler.js', 'main.js', 'imported-via-special-handler.js', null); +esSpecialHandler(someVariable, 'main.js', 'null', null); +esSpecialHandler(someCustomlyResolvedVariable, 'main.js', 'null', someCustomlyResolvedVariable); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/system/generated-imported-via-special-handler.js b/test/chunking-form/samples/render-dynamic-import/_expected/system/generated-imported-via-special-handler.js new file mode 100644 index 00000000000..8dd5d86ac58 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/system/generated-imported-via-special-handler.js @@ -0,0 +1,10 @@ +System.register([], function () { + 'use strict'; + return { + execute: function () { + + console.log('special'); + + } + }; +}); diff --git a/test/chunking-form/samples/render-dynamic-import/_expected/system/main.js b/test/chunking-form/samples/render-dynamic-import/_expected/system/main.js new file mode 100644 index 00000000000..52306b00fc1 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/_expected/system/main.js @@ -0,0 +1,12 @@ +System.register([], function (exports, module) { + 'use strict'; + return { + execute: function () { + + systemSpecialHandler('./generated-imported-via-special-handler.js', 'main.js', 'imported-via-special-handler.js', null); + systemSpecialHandler(someVariable, 'main.js', 'null', null); + systemSpecialHandler(someCustomlyResolvedVariable, 'main.js', 'null', someCustomlyResolvedVariable); + + } + }; +}); diff --git a/test/chunking-form/samples/render-dynamic-import/imported-via-special-handler.js b/test/chunking-form/samples/render-dynamic-import/imported-via-special-handler.js new file mode 100644 index 00000000000..6c8553ec696 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/imported-via-special-handler.js @@ -0,0 +1 @@ +console.log('special'); diff --git a/test/chunking-form/samples/render-dynamic-import/main.js b/test/chunking-form/samples/render-dynamic-import/main.js new file mode 100644 index 00000000000..72129c63bd5 --- /dev/null +++ b/test/chunking-form/samples/render-dynamic-import/main.js @@ -0,0 +1,3 @@ +import('./imported-via-special-handler.js'); +import(someVariable); +import(someResolvedVariable);