From 2d17db991f098d3694263a1d1483c3d6e2a84db8 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 12 Aug 2018 10:50:47 +0200 Subject: [PATCH] Plugin system refactoring (#2382) * asset fixes and refactoring for caching * revert cacheType * refactor plugin hooks behind a single interface, plugin error augmentations * ensureArray -> set checking external * remove name handling and cache code * pr feedback * Remove unnecessary export --- bin/src/run/build.ts | 10 +- src/Chunk.ts | 5 +- src/Graph.ts | 171 +++++------- src/Module.ts | 19 +- src/rollup/index.ts | 97 +++---- src/rollup/types.d.ts | 8 +- src/utils/addons.ts | 84 +++--- src/utils/{defaults.ts => default-plugin.ts} | 47 +--- src/utils/ensureArray.ts | 7 - src/utils/error.ts | 35 ++- src/utils/first-sync.ts | 11 - src/utils/first.ts | 13 - src/utils/mergeOptions.ts | 4 +- src/utils/pluginDriver.ts | 194 ++++++++++++++ src/utils/promise.ts | 18 -- src/utils/transform.ts | 251 +++++++----------- src/utils/transformChunk.ts | 104 ++++---- src/watch/index.ts | 11 +- .../custom-path-resolver-plural-b/_config.js | 5 +- .../external-function-always-true/_config.js | 12 +- .../external-function-always-true/main.js | 2 +- .../plugin-error-loc-instead-pos/_config.js | 1 + .../_config.js | 1 + .../_config.js | 1 + test/function/samples/plugin-error/_config.js | 1 + .../plugin-warn-loc-instead-pos/_config.js | 1 + test/function/samples/plugin-warn/_config.js | 2 + .../transform-without-sourcemap/_config.js | 8 +- 28 files changed, 576 insertions(+), 547 deletions(-) rename src/utils/{defaults.ts => default-plugin.ts} (69%) delete mode 100644 src/utils/ensureArray.ts delete mode 100644 src/utils/first-sync.ts delete mode 100644 src/utils/first.ts create mode 100644 src/utils/pluginDriver.ts delete mode 100644 src/utils/promise.ts diff --git a/bin/src/run/build.ts b/bin/src/run/build.ts index d0242cf60c4..339e1734354 100644 --- a/bin/src/run/build.ts +++ b/bin/src/run/build.ts @@ -3,13 +3,10 @@ import * as rollup from 'rollup'; import tc from 'turbocolor'; import { InputOptions, - OutputBundle, - OutputChunk, OutputOptions, RollupBuild, RollupSingleFileBuild } from '../../../src/rollup/types'; -import { mapSequence } from '../../../src/utils/promise'; import relativeId from '../../../src/utils/relativeId'; import { handleError, stderr } from '../logging'; import SOURCEMAPPING_URL from '../sourceMappingUrl'; @@ -67,10 +64,9 @@ export default function build( }); } - return mapSequence>( - outputOptions, - output => bundle.write(output) - ).then(() => bundle); + return Promise.all(outputOptions.map(output => >bundle.write(output))).then( + () => bundle + ); }) .then((bundle?: RollupSingleFileBuild | RollupBuild) => { warnings.flush(); diff --git a/src/Chunk.ts b/src/Chunk.ts index 08ce51a49b5..d90be4e6f20 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -1115,10 +1115,7 @@ export default class Chunk { else if (options.dir) file = resolve(options.dir, this.id); else file = resolve(this.id); - if ( - this.graph.hasLoaders || - this.graph.plugins.find(plugin => Boolean(plugin.transform || plugin.transformBundle)) - ) { + if (this.graph.pluginDriver.hasLoadersOrTransforms) { const decodedMap = magicString.generateDecodedMap({}); map = collapseSourcemaps(this, file, decodedMap, this.usedModules, chunkSourcemapChain); } else { diff --git a/src/Graph.ts b/src/Graph.ts index e477bc6af9e..ec94a53a293 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -12,23 +12,15 @@ import { Asset, InputOptions, IsExternal, - LoadHook, ModuleJSON, OutputBundle, - Plugin, - PluginContext, - ResolveDynamicImportHook, - ResolveIdHook, - RollupError, RollupWarning, SourceDescription, TreeshakingOptions, WarningHandler, Watcher } from './rollup/types'; -import { createAssetPluginHooks, finaliseAsset } from './utils/assetHooks'; -import { load, makeOnwarn, resolveId } from './utils/defaults'; -import ensureArray from './utils/ensureArray'; +import { finaliseAsset } from './utils/assetHooks'; import { randomUint8Array, Uint8ArrayEqual, @@ -36,12 +28,23 @@ import { Uint8ArrayXor } from './utils/entryHashing'; import error from './utils/error'; -import first from './utils/first'; import { isRelative, relative, resolve } from './utils/path'; +import { createPluginDriver, PluginDriver } from './utils/pluginDriver'; import relativeId, { getAliasName } from './utils/relativeId'; import { timeEnd, timeStart } from './utils/timers'; import transform from './utils/transform'; +function makeOnwarn() { + const warned = Object.create(null); + + return (warning: any) => { + const str = warning.toString(); + if (str in warned) return; + console.error(str); //eslint-disable-line no-console + warned[str] = true; + }; +} + export default class Graph { curChunkIndex = 0; acornOptions: acorn.Options; @@ -51,26 +54,24 @@ export default class Graph { externalModules: ExternalModule[] = []; getModuleContext: (id: string) => string; hasLoaders: boolean; - isExternal: IsExternal; isPureExternalModule: (id: string) => boolean; - load: LoadHook; moduleById = new Map(); assetsById = new Map(); modules: Module[] = []; onwarn: WarningHandler; - plugins: Plugin[]; - pluginContext: PluginContext; deoptimizationTracker: EntityPathTracker; - resolveDynamicImport: ResolveDynamicImportHook; - resolveId: (id: string, parent: string) => Promise; scope: GlobalScope; shimMissingExports: boolean; exportShimVariable: GlobalVariable; treeshakingOptions: TreeshakingOptions; varOrConst: 'var' | 'const'; + isExternal: IsExternal; + contextParse: (code: string, acornOptions?: acorn.Options) => Program; + pluginDriver: PluginDriver; + // deprecated treeshake: boolean; @@ -80,15 +81,11 @@ export default class Graph { this.cachedModules = new Map(); if (options.cache) { if (options.cache.modules) { - options.cache.modules.forEach(module => { - this.cachedModules.set(module.id, module); - }); + for (const module of options.cache.modules) this.cachedModules.set(module.id, module); } } delete options.cache; // TODO not deleting it here causes a memory leak; needs further investigation - this.plugins = options.plugins; - if (!options.input) { throw new Error('You must supply options.input to rollup'); } @@ -121,44 +118,15 @@ export default class Graph { return this.acornParse(code, { ...defaultAcornOptions, ...options, ...this.acornOptions }); }; - const assetPluginHooks = createAssetPluginHooks(this.assetsById); - - this.pluginContext = { - watcher, - isExternal: undefined, - resolveId: undefined, - parse: this.contextParse, - warn: (warning: RollupWarning | string) => { - if (typeof warning === 'string') warning = { message: warning }; - this.warn(warning); - }, - error: (err: RollupError | string) => { - if (typeof err === 'string') throw new Error(err); - error(err); - }, - ...assetPluginHooks - }; + this.pluginDriver = createPluginDriver(this, options, watcher); - this.resolveId = first( - [ - ((id: string, parentId: string) => - this.isExternal(id, parentId, false) ? false : null) as ResolveIdHook - ] - .concat( - this.plugins - .map(plugin => plugin.resolveId) - .filter(Boolean) - .map(resolveId => resolveId.bind(this.pluginContext)) - ) - .concat(resolveId(options)) - ); - - this.pluginContext.resolveId = this.resolveId; - - const loaders = this.plugins.map(plugin => plugin.load).filter(Boolean); - this.hasLoaders = loaders.length !== 0; - - this.load = first([...loaders, load]); + if (typeof options.external === 'function') { + this.isExternal = options.external; + } else { + const external = options.external; + const ids = new Set(Array.isArray(external) ? external : external ? [external] : []); + this.isExternal = id => ids.has(id); + } this.shimMissingExports = options.shimMissingExports; @@ -184,14 +152,6 @@ export default class Graph { this.getModuleContext = () => this.context; } - if (typeof options.external === 'function') { - this.isExternal = options.external; - } else { - const ids = ensureArray(options.external); - this.isExternal = id => ids.indexOf(id) !== -1; - } - this.pluginContext.isExternal = this.isExternal; - this.onwarn = options.onwarn || makeOnwarn(); this.varOrConst = options.preferConst ? 'const' : 'var'; @@ -199,14 +159,6 @@ export default class Graph { this.acornOptions = options.acorn || {}; const acornPluginsToInject = []; - this.resolveDynamicImport = first( - [ - ...this.plugins.map(plugin => plugin.resolveDynamicImport).filter(Boolean), - function(specifier, parentId) { - return typeof specifier === 'string' && this.resolveId(specifier, parentId); - } - ].map(resolveDynamicImport => resolveDynamicImport.bind(this.pluginContext)) - ); acornPluginsToInject.push(injectDynamicImportPlugin); acornPluginsToInject.push(injectImportMeta); this.acornOptions.plugins = this.acornOptions.plugins || {}; @@ -217,7 +169,14 @@ export default class Graph { (this.acornOptions).allowAwaitOutsideFunction = true; } - acornPluginsToInject.push(...ensureArray(options.acornInjectPlugins)); + const acornInjectPlugins = options.acornInjectPlugins; + acornPluginsToInject.push( + ...(Array.isArray(acornInjectPlugins) + ? acornInjectPlugins + : acornInjectPlugins + ? [acornInjectPlugins] + : []) + ); this.acornParse = acornPluginsToInject.reduce((acc, plugin) => plugin(acc), acorn).parse; } @@ -244,23 +203,25 @@ export default class Graph { } private loadModule(entryName: string) { - return this.resolveId(entryName, undefined).then(id => { - if (id === false) { - error({ - code: 'UNRESOLVED_ENTRY', - message: `Entry module cannot be external` - }); - } + return this.pluginDriver + .hookFirst('resolveId', [entryName, undefined]) + .then(id => { + if (id === false) { + error({ + code: 'UNRESOLVED_ENTRY', + message: `Entry module cannot be external` + }); + } - if (id == null) { - error({ - code: 'UNRESOLVED_ENTRY', - message: `Could not resolve entry (${entryName})` - }); - } + if (id == null) { + error({ + code: 'UNRESOLVED_ENTRY', + message: `Could not resolve entry (${entryName})` + }); + } - return this.fetchModule(id, undefined); - }); + return this.fetchModule(id, undefined); + }); } private link() { @@ -647,7 +608,7 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup this.moduleById.set(id, module); timeStart('load modules', 3); - return Promise.resolve(this.load.call(this.pluginContext, id)) + return Promise.resolve(this.pluginDriver.hookFirst('load', [id])) .catch((err: Error) => { timeEnd('load modules', 3); let msg = `Could not load ${id}`; @@ -683,7 +644,7 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup // re-emit transform assets if (cachedModule.transformAssets) { for (const asset of cachedModule.transformAssets) { - this.pluginContext.emitAsset( + this.pluginDriver.emitAsset( asset.name, (asset.dependencies ? asset.dependencies : asset.source), asset.dependencies && asset.source @@ -693,7 +654,7 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup return cachedModule; } - return transform(this, sourceDescription, module, this.plugins); + return transform(this, sourceDescription, module); }) .then((source: ModuleJSON) => { module.setSource(source); @@ -742,7 +703,7 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup const fetchDynamicImportsPromise = Promise.all( module.getDynamicImportExpressions().map((dynamicImportExpression, index) => { return Promise.resolve( - this.resolveDynamicImport.call(this.pluginContext, dynamicImportExpression, module.id) + this.pluginDriver.hookFirst('resolveDynamicImport', [dynamicImportExpression, module.id]) ).then(replacement => { if (!replacement) { module.dynamicImportResolutions[index] = { @@ -783,17 +744,24 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup return Promise.all( module.sources.map(source => { - const resolvedId = module.resolvedIds[source]; - return (resolvedId ? Promise.resolve(resolvedId) : this.resolveId(source, module.id)).then( - resolvedId => { + return Promise.resolve() + .then(() => { + const resolvedId = module.resolvedIds[source]; + if (resolvedId) return resolvedId; + const isExternal = this.isExternal(source, module.id, false); + if (isExternal) return false; + return this.pluginDriver.hookFirst('resolveId', [ + source, + module.id + ]); + }) + .then(resolvedId => { // TODO types of `resolvedId` are not compatable with 'externalId'. // `this.resolveId` returns `string`, `void`, and `boolean` const externalId = resolvedId || (isRelative(source) ? resolve(module.id, '..', source) : source); - let isExternal = - resolvedId === false || - this.isExternal.call(this.pluginContext, externalId, module.id, true); + let isExternal = resolvedId === false || this.isExternal(externalId, module.id, true); if (!resolvedId && !isExternal) { if (isRelative(source)) { @@ -849,8 +817,7 @@ Try defining "${chunkName}" first in the manualChunks definitions of the Rollup module.resolvedIds[source] = resolvedId; return this.fetchModule(resolvedId, module.id); } - } - ); + }); }) ).then(() => fetchDynamicImportsPromise); } diff --git a/src/Module.ts b/src/Module.ts index 1539d0023b6..b53ee6c1e12 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -30,7 +30,6 @@ import Chunk from './Chunk'; import ExternalModule from './ExternalModule'; import Graph from './Graph'; import { Asset, IdMap, ModuleJSON, RawSourceMap, RollupError, RollupWarning } from './rollup/types'; -import { handleMissingExport } from './utils/defaults'; import error from './utils/error'; import getCodeFrame from './utils/getCodeFrame'; import { getOriginalLocation } from './utils/getOriginalLocation'; @@ -144,6 +143,22 @@ function includeFully(node: Node) { } } +function handleMissingExport( + exportName: string, + importingModule: Module, + importedModule: string, + importerStart?: number +) { + importingModule.error( + { + code: 'MISSING_EXPORT', + message: `'${exportName}' is not exported by ${relativeId(importedModule)}`, + url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` + }, + importerStart + ); +} + export default class Module { type: 'Module'; private graph: Graph; @@ -262,7 +277,7 @@ export default class Module { code, // Only needed for debugging error: this.error.bind(this), fileName, // Needed for warnings - getAssetFileName: this.graph.pluginContext.getAssetFileName, + getAssetFileName: this.graph.pluginDriver.getAssetFileName, getExports: this.getExports.bind(this), getReexports: this.getReexports.bind(this), getModuleExecIndex: () => this.execIndex, diff --git a/src/rollup/index.ts b/src/rollup/index.ts index 17a2680db82..0076ab28671 100644 --- a/src/rollup/index.ts +++ b/src/rollup/index.ts @@ -5,13 +5,11 @@ import { createAddons } from '../utils/addons'; import { createAssetPluginHooks, finaliseAsset } from '../utils/assetHooks'; import commondir from '../utils/commondir'; import { Deprecation } from '../utils/deprecateOptions'; -import ensureArray from '../utils/ensureArray'; import error from '../utils/error'; import { writeFile } from '../utils/fs'; import getExportMode from '../utils/getExportMode'; import mergeOptions, { GenericConfigObject } from '../utils/mergeOptions'; import { basename, dirname, resolve } from '../utils/path'; -import { mapSequence } from '../utils/promise'; import { SOURCEMAPPING_URL } from '../utils/sourceMappingURL'; import { getTimings, initialiseTimers, timeEnd, timeStart } from '../utils/timers'; import { Watcher } from '../watch'; @@ -77,18 +75,6 @@ function applyOptionHook(inputOptions: InputOptions, plugin: Plugin) { return inputOptions; } -function applyBuildStartHook(graph: Graph) { - return Promise.all( - graph.plugins.map(plugin => plugin.buildStart && plugin.buildStart.call(graph.pluginContext)) - ).then(() => {}); -} - -function applyBuildEndHook(graph: Graph, err?: any) { - return Promise.all( - graph.plugins.map(plugin => plugin.buildEnd && plugin.buildEnd.call(graph.pluginContext, err)) - ).then(() => {}); -} - function getInputOptions(rawInputOptions: GenericConfigObject): any { if (!rawInputOptions) { throw new Error('You must supply an options object to rollup'); @@ -102,7 +88,8 @@ function getInputOptions(rawInputOptions: GenericConfigObject): any { if (deprecations.length) addDeprecations(deprecations, inputOptions.onwarn); checkInputOptions(inputOptions); - inputOptions.plugins = ensureArray(inputOptions.plugins); + const plugins = inputOptions.plugins; + inputOptions.plugins = Array.isArray(plugins) ? plugins : plugins ? [plugins] : []; inputOptions = inputOptions.plugins.reduce(applyOptionHook, inputOptions); if (!inputOptions.experimentalCodeSplitting) { @@ -179,7 +166,8 @@ export default function rollup( timeStart('BUILD', 1); - return applyBuildStartHook(graph) + return graph.pluginDriver + .hookParallel('buildStart') .then(() => graph.build( inputOptions.input, @@ -190,11 +178,11 @@ export default function rollup( ) .then( chunks => - applyBuildEndHook(graph).then(() => { + graph.pluginDriver.hookParallel('buildEnd').then(() => { return chunks; }), err => - applyBuildEndHook(graph, err).then(() => { + graph.pluginDriver.hookParallel('buildEnd', err).then(() => { throw err; }) ) @@ -339,49 +327,37 @@ export default function rollup( outputChunk.code = rendered.code; outputChunk.map = rendered.map; - return Promise.all( - graph.plugins - .filter(plugin => plugin.ongenerate) - .map(plugin => - plugin.ongenerate.call( - graph.pluginContext, - { bundle: outputChunk, ...outputOptions }, - outputChunk - ) - ) - ); + return graph.pluginDriver.hookParallel('ongenerate', [ + { bundle: outputChunk, ...outputOptions }, + outputChunk + ]); }); }) ).then(() => {}); }) .then(() => { // run generateBundle hook - const generateBundlePlugins = graph.plugins.filter(plugin => plugin.generateBundle); - if (generateBundlePlugins.length === 0) return; // assets emitted during generateBundle are unique to that specific generate call const assets = new Map(graph.assetsById); - const generateBundleContext = { - ...graph.pluginContext, - ...createAssetPluginHooks(assets, outputBundle, assetFileNames) - }; - - return Promise.all( - generateBundlePlugins.map(plugin => - plugin.generateBundle.call( - generateBundleContext, - outputOptions, - outputBundle, - isWrite - ) - ) - ).then(() => { - // throw errors for assets not finalised with a source - assets.forEach(asset => { - if (asset.fileName === undefined) - finaliseAsset(asset, outputBundle, assetFileNames); + const generateAssetPluginHooks = createAssetPluginHooks( + assets, + outputBundle, + assetFileNames + ); + + return graph.pluginDriver + .hookSeq('generateBundle', [outputOptions, outputBundle, isWrite], context => ({ + ...context, + ...generateAssetPluginHooks + })) + .then(() => { + // throw errors for assets not finalised with a source + assets.forEach(asset => { + if (asset.fileName === undefined) + finaliseAsset(asset, outputBundle, assetFileNames); + }); }); - }); }) .then(() => { timeEnd('GENERATE', 1); @@ -489,18 +465,13 @@ function writeOutputFile( .then( () => isOutputChunk(outputFile) && - mapSequence(graph.plugins.filter(plugin => plugin.onwrite), (plugin: Plugin) => { - return Promise.resolve( - plugin.onwrite.call( - graph.pluginContext, - { - bundle: build, - ...outputOptions - }, - outputFile - ) - ); - }) + graph.pluginDriver.hookSeq('onwrite', [ + { + bundle: build, + ...outputOptions + }, + outputFile + ]) ) .then(() => {}); } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index e2ad23be8e1..d8da1c2a6ca 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -23,6 +23,7 @@ export interface RollupError { pos?: number; plugin?: string; pluginCode?: string; + hook?: string; } export interface ExistingRawSourceMap { @@ -99,11 +100,7 @@ export type ResolveIdHook = ( parent: string ) => Promise | string | boolean | void | null; -export type IsExternal = ( - id: string, - parentId: string, - isResolved: boolean -) => Promise | boolean | void; +export type IsExternal = (id: string, parentId: string, isResolved: boolean) => boolean | void; export type LoadHook = ( this: PluginContext, @@ -328,6 +325,7 @@ export interface RollupWarning { plugin?: string; pos?: number; pluginCode?: string; + hook?: string; } export type WarningHandler = (warning: string | RollupWarning) => void; diff --git a/src/utils/addons.ts b/src/utils/addons.ts index 7987de96d0f..fff9a8eea70 100644 --- a/src/utils/addons.ts +++ b/src/utils/addons.ts @@ -10,57 +10,45 @@ export interface Addons { hash: Uint8Array; } -export function createAddons(graph: Graph, options: OutputOptions): Promise { - return Promise.all([ - collectAddon(graph, options.banner, 'banner', '\n'), - collectAddon(graph, options.footer, 'footer', '\n'), - collectAddon(graph, options.intro, 'intro', '\n\n'), - collectAddon(graph, options.outro, 'outro', '\n\n') - ]).then(([banner, footer, intro, outro]) => { - if (intro) intro += '\n\n'; - if (outro) outro = `\n\n${outro}`; - - if (banner.length) banner += '\n'; - if (footer.length) footer = '\n' + footer; - - const hash = new Uint8Array(4); - - return { intro, outro, banner, footer, hash }; - }); +function evalIfFn(strOrFn: T | (() => T | Promise)) { + switch (typeof strOrFn) { + case 'function': + return (<() => T | Promise>strOrFn)(); + case 'string': + return strOrFn; + default: + return ''; + } } -function collectAddon( - graph: Graph, - initialAddon: string | (() => string | Promise), - addonName: 'banner' | 'footer' | 'intro' | 'outro', - sep: string -) { - return Promise.all( - [ - { - pluginName: 'rollup', - source: initialAddon - }, - ...graph.plugins.map((plugin, idx) => ({ - pluginName: plugin.name || `Plugin at pos ${idx}`, - source: plugin[addonName] - })) - ].map(({ pluginName, source }, idx) => { - if (!source) return; +const concatSep = (out: string, next: string) => (next ? `${out}\n${next}` : out); +const concatDblSep = (out: string, next: string) => (next ? `${out}\n\n${next}` : out); - if (typeof source === 'string') return source; - - return Promise.resolve() - .then(() => { - return source.call(idx !== 0 && graph.pluginContext); - }) - .catch(err => { - error({ - code: 'ADDON_ERROR', - message: `Could not retrieve ${addonName}. Check configuration of ${pluginName}. +export function createAddons(graph: Graph, options: OutputOptions): Promise { + const pluginDriver = graph.pluginDriver; + return Promise.all([ + pluginDriver.hookReduceValue('banner', evalIfFn(options.banner), [], concatSep), + pluginDriver.hookReduceValue('footer', evalIfFn(options.footer), [], concatSep), + pluginDriver.hookReduceValue('intro', evalIfFn(options.intro), [], concatDblSep), + pluginDriver.hookReduceValue('outro', evalIfFn(options.outro), [], concatDblSep) + ]) + .then(([banner, footer, intro, outro]) => { + if (intro) intro += '\n\n'; + if (outro) outro = `\n\n${outro}`; + if (banner.length) banner += '\n'; + if (footer.length) footer = '\n' + footer; + + const hash = new Uint8Array(4); + + return { intro, outro, banner, footer, hash }; + }) + .catch( + (err): any => { + error({ + code: 'ADDON_ERROR', + message: `Could not retrieve ${err.hook}. Check configuration of ${err.plugin}. \tError Message: ${err.message}` - }); }); - }) - ).then(addons => addons.filter(Boolean).join(sep)); + } + ); } diff --git a/src/utils/defaults.ts b/src/utils/default-plugin.ts similarity index 69% rename from src/utils/defaults.ts rename to src/utils/default-plugin.ts index 542fe7c3e76..ceebc95fba4 100644 --- a/src/utils/defaults.ts +++ b/src/utils/default-plugin.ts @@ -1,12 +1,20 @@ -import Module from '../Module'; -import { InputOptions } from '../rollup/types'; +import { InputOptions, Plugin } from '../rollup/types'; import error from './error'; import { lstatSync, readdirSync, readFileSync, realpathSync } from './fs'; // eslint-disable-line import { basename, dirname, isAbsolute, resolve } from './path'; -import relativeId from './relativeId'; -export function load(id: string) { - return readFileSync(id, 'utf-8'); +export function getRollupDefaultPlugin(options: InputOptions): Plugin { + return { + name: 'Rollup Core', + resolveId: createResolveId(options), + load(id) { + return readFileSync(id, 'utf-8'); + }, + resolveDynamicImport(specifier, parentId) { + if (typeof specifier === 'string') + return >this.resolveId(specifier, parentId); + } + }; } function findFile(file: string, preserveSymlinks: boolean): string | void { @@ -35,7 +43,7 @@ function addJsExtensionIfNecessary(file: string, preserveSymlinks: boolean) { return found; } -export function resolveId(options: InputOptions) { +function createResolveId(options: InputOptions) { return function(importee: string, importer: string) { if (typeof process === 'undefined') { error({ @@ -59,30 +67,3 @@ export function resolveId(options: InputOptions) { ); }; } - -export function makeOnwarn() { - const warned = Object.create(null); - - return (warning: any) => { - const str = warning.toString(); - if (str in warned) return; - console.error(str); //eslint-disable-line no-console - warned[str] = true; - }; -} - -export function handleMissingExport( - exportName: string, - importingModule: Module, - importedModule: string, - importerStart?: number -) { - importingModule.error( - { - code: 'MISSING_EXPORT', - message: `'${exportName}' is not exported by ${relativeId(importedModule)}`, - url: `https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` - }, - importerStart - ); -} diff --git a/src/utils/ensureArray.ts b/src/utils/ensureArray.ts deleted file mode 100644 index ebe15ba561c..00000000000 --- a/src/utils/ensureArray.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default function ensureArray(thing: T[]): T[]; -export default function ensureArray(thing: any): any[]; -export default function ensureArray(thing: T[] | any): T[] | any[] { - if (Array.isArray(thing)) return thing; - if (thing == undefined) return []; - return [thing]; -} diff --git a/src/utils/error.ts b/src/utils/error.ts index 69835c25be2..b9e94049bb1 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,13 +1,30 @@ -import { RollupError } from '../rollup/types'; +import { locate } from 'locate-character'; +import { RollupError, RollupWarning } from '../rollup/types'; +import getCodeFrame from './getCodeFrame'; -export default function error(props: Error | RollupError) { - if (props instanceof Error) throw props; - - const err = new Error(props.message); +export default function error(base: Error | RollupError, props?: RollupError) { + if (base instanceof Error === false) base = Object.assign(new Error(base.message), base); + if (props) Object.assign(base, props); + throw base; +} - Object.keys(props).forEach(key => { - (err)[key] = (props)[key]; - }); +export function augmentCodeLocation( + object: RollupError | RollupWarning, + pos: { line: number; column: number }, + source: string, + id: string +): void { + if (pos.line !== undefined && pos.column !== undefined) { + const { line, column } = pos; + object.loc = { file: id, line, column }; + } else { + object.pos = pos; + const { line, column } = locate(source, pos, { offsetLine: 1 }); + object.loc = { file: id, line, column }; + } - throw err; + if (object.frame === undefined) { + const { line, column } = object.loc; + object.frame = getCodeFrame(source, line, column); + } } diff --git a/src/utils/first-sync.ts b/src/utils/first-sync.ts deleted file mode 100644 index 426a1dc31d2..00000000000 --- a/src/utils/first-sync.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Return the first non-null or -undefined result from an array of -// sync functions -export default function firstSync( - candidates: ((...args: any[]) => T | void)[] -): (...args: any[]) => T | void { - return function(this: Context, ...args: any[]) { - return candidates.reduce((result, candidate) => { - return result != null ? result : candidate.call(this, ...args); - }, null); - }; -} diff --git a/src/utils/first.ts b/src/utils/first.ts deleted file mode 100644 index 8c7b9f3d56b..00000000000 --- a/src/utils/first.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Return the first non-null or -undefined result from an array of -// maybe-sync, maybe-promise-returning functions -export default function first( - candidates: ((...args: any[]) => Promise | T | void)[] -): (...args: any[]) => Promise { - return function(this: Context, ...args: any[]) { - return candidates.reduce((promise, candidate) => { - return promise.then( - result => (result != null ? result : Promise.resolve(candidate.call(this, ...args))) - ); - }, Promise.resolve()); - }; -} diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index 56e6498a094..643a2cba0f9 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -1,6 +1,5 @@ import { InputOptions, OutputOptions, WarningHandler } from '../rollup/types'; import deprecateOptions, { Deprecation } from './deprecateOptions'; -import ensureArray from './ensureArray'; export interface GenericConfigObject { [key: string]: any; @@ -104,7 +103,8 @@ export default function mergeOptions({ Object.assign(command, command.output); } - const normalizedOutputOptions = ensureArray(config.output); + const output = config.output; + const normalizedOutputOptions = Array.isArray(output) ? output : output ? [output] : []; if (normalizedOutputOptions.length === 0) normalizedOutputOptions.push({}); const outputOptions = normalizedOutputOptions.map(singleOutputOptions => getOutputOptions(singleOutputOptions, command) diff --git a/src/utils/pluginDriver.ts b/src/utils/pluginDriver.ts new file mode 100644 index 00000000000..1d460131ee8 --- /dev/null +++ b/src/utils/pluginDriver.ts @@ -0,0 +1,194 @@ +import Graph from '../Graph'; +import { + InputOptions, + Plugin, + PluginContext, + RollupError, + RollupWarning, + Watcher +} from '../rollup/types'; +import { createAssetPluginHooks, EmitAsset } from './assetHooks'; +import { getRollupDefaultPlugin } from './default-plugin'; +import error from './error'; + +export interface PluginDriver { + emitAsset: EmitAsset; + getAssetFileName(assetId: string): string; + hookSeq(hook: string, args?: any[], context?: HookContext): Promise; + hookFirst(hook: string, args?: any[], hookContext?: HookContext): Promise; + hookParallel(hook: string, args?: any[], hookContext?: HookContext): Promise; + hookReduceArg0( + hook: string, + args: any[], + reduce: Reduce, + hookContext?: HookContext + ): Promise; + hookReduceValue( + hook: string, + value: T | Promise, + args: any[], + reduce: Reduce, + hookContext?: HookContext + ): Promise; + hasLoadersOrTransforms: boolean; +} + +export type Reduce = (reduction: T, result: R, plugin: Plugin) => T; +export type HookContext = (context: PluginContext, plugin?: Plugin) => PluginContext; + +export function createPluginDriver( + graph: Graph, + options: InputOptions, + watcher?: Watcher +): PluginDriver { + const plugins = [...(options.plugins || []), getRollupDefaultPlugin(options)]; + const { emitAsset, getAssetFileName, setAssetSource } = createAssetPluginHooks(graph.assetsById); + + let hasLoadersOrTransforms = false; + + const pluginContexts = plugins.map(plugin => { + if ( + !hasLoadersOrTransforms && + (plugin.load || plugin.transform || plugin.transformBundle || plugin.transformChunk) + ) + hasLoadersOrTransforms = true; + + const context: PluginContext = { + watcher, + isExternal(id: string, parentId: string, isResolved = false) { + return graph.isExternal(id, parentId, isResolved); + }, + resolveId(id: string, parent: string) { + return pluginDriver.hookFirst('resolveId', [id, parent]); + }, + parse: graph.contextParse, + emitAsset, + getAssetFileName, + setAssetSource, + + warn: (warning: RollupWarning | string) => { + if (typeof warning === 'string') warning = { message: warning }; + if (warning.code) warning.pluginCode = warning.code; + warning.code = 'PLUGIN_WARNING'; + warning.plugin = plugin.name || '(anonymous plugin)'; + graph.warn(warning); + }, + error: (err: RollupError | string) => { + if (typeof err === 'string') err = { message: err }; + if (err.code) err.pluginCode = err.code; + err.code = 'PLUGIN_ERROR'; + err.plugin = plugin.name || '(anonymous plugin)'; + error(err); + } + }; + return context; + }); + + function runHook( + hookName: string, + args: any[], + pidx: number, + permitValues = false, + hookContext?: HookContext + ): Promise { + const plugin = plugins[pidx]; + let context = pluginContexts[pidx]; + const hook = (plugin)[hookName]; + if (!hook) return; + if (hookContext) { + context = hookContext(context, plugin); + if (!context || context === pluginContexts[pidx]) + throw new Error('Internal Rollup error: hookContext must return a new context object.'); + } + return Promise.resolve() + .then(() => { + // permit values allows values to be returned instead of a functional hook + if (typeof hook !== 'function') { + if (permitValues) return hook; + error({ + code: 'INVALID_PLUGIN_HOOK', + message: `Error running plugin hook ${hookName} for ${plugin.name || + `Plugin at pos ${pidx + 1}`}, expected a function hook.` + }); + } + return hook.apply(context, args); + }) + .catch(err => { + if (typeof err === 'string') err = { message: err }; + if (err.code !== 'PLUGIN_ERROR') { + if (err.code) err.pluginCode = err.code; + err.code = 'PLUGIN_ERROR'; + } + err.plugin = plugin.name || `Plugin at pos ${pidx}`; + err.hook = hookName; + error(err); + }); + } + + const pluginDriver: PluginDriver = { + emitAsset, + getAssetFileName, + hasLoadersOrTransforms, + + // chains, ignores returns + hookSeq(name, args, hookContext) { + let promise: Promise = Promise.resolve(); + for (let i = 0; i < plugins.length; i++) + promise = promise.then(() => { + return runHook(name, args, i, false, hookContext); + }); + return promise; + }, + // chains, first non-null result stops and returns + hookFirst(name, args, hookContext) { + let promise: Promise = Promise.resolve(); + for (let i = 0; i < plugins.length; i++) { + promise = promise.then((result: any) => { + if (result != null) return result; + return runHook(name, args, i, false, hookContext); + }); + } + return promise; + }, + // parallel, ignores returns + hookParallel(name, args, hookContext) { + const promises: Promise[] = []; + for (let i = 0; i < plugins.length; i++) { + const hookPromise = runHook(name, args, i, false, hookContext); + if (!hookPromise) continue; + promises.push(hookPromise); + } + return Promise.all(promises).then(() => {}); + }, + // chains, reduces returns of type R, to type T, handling the reduced value as the first hook argument + hookReduceArg0(name, [arg0, ...args], reduce, hookContext) { + let promise = Promise.resolve(arg0); + for (let i = 0; i < plugins.length; i++) { + promise = promise.then(arg0 => { + const hookPromise = runHook(name, [arg0, ...args], i, false, hookContext); + if (!hookPromise) return arg0; + return hookPromise.then((result: any) => { + return reduce(arg0, result, plugins[i]); + }); + }); + } + return promise; + }, + // chains, reduces returns of type R, to type T, handling the reduced value separately. permits hooks as values. + hookReduceValue(name, initial, args, reduce, hookContext) { + let promise = Promise.resolve(initial); + for (let i = 0; i < plugins.length; i++) { + promise = promise.then(value => { + const hookPromise = runHook(name, args, i, true, hookContext); + if (!hookPromise) return value; + return hookPromise.then((result: any) => { + return reduce(value, result, plugins[i]); + }); + }); + } + return promise; + } + }; + + return pluginDriver; +} diff --git a/src/utils/promise.ts b/src/utils/promise.ts deleted file mode 100644 index 9d3cd2cb914..00000000000 --- a/src/utils/promise.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function mapSequence(array: T[], fn: (member: T) => Promise | U): Promise { - const results: U[] = []; - let promise: Promise = Promise.resolve(); - - function next(member: T, i: number) { - return Promise.resolve(fn(member)).then(value => (results[i] = value)); - } - - for (let i = 0; i < array.length; i += 1) { - promise = promise.then(() => next(array[i], i)); - } - - return promise.then(() => results); -} - -export function runSequence(array: T[]) { - return mapSequence(array, i => i); -} diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 5000d1a6b1d..4a191f0d38d 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -1,203 +1,142 @@ import * as ESTree from 'estree'; -import { locate } from 'locate-character'; import { decode } from 'sourcemap-codec'; import Program from '../ast/nodes/Program'; import Graph from '../Graph'; import Module from '../Module'; import { + Asset, Plugin, - PluginContext, RawSourceMap, RollupError, RollupWarning, TransformSourceDescription } from '../rollup/types'; import { createTransformEmitAsset, EmitAsset } from './assetHooks'; -import error from './error'; -import getCodeFrame from './getCodeFrame'; +import error, { augmentCodeLocation } from './error'; import { dirname, resolve } from './path'; -function augmentCodeLocation({ - object, - pos, - code, - id, - source, - pluginName -}: { - object: T; - pos: { line: number; column: number }; - code: string; - id: string; - source: string; - pluginName: string; -}): T { - if (object.code) object.pluginCode = object.code; - object.code = code; - - if (pos !== undefined) { - if (pos.line !== undefined && pos.column !== undefined) { - const { line, column } = pos; - object.loc = { file: id, line, column }; - } else { - object.pos = pos; - const { line, column } = locate(source, pos, { offsetLine: 1 }); - object.loc = { file: id, line, column }; - } - - if (object.frame === undefined) { - const { line, column } = object.loc; - object.frame = getCodeFrame(source, line, column); - } - } - - object.plugin = pluginName; - object.id = id; - - return object; -} - -function createPluginTransformContext( - graph: Graph, - plugin: Plugin, - id: string, - source: string, - emitAsset: EmitAsset -): PluginContext { - return { - ...graph.pluginContext, - warn(warning: RollupWarning | string, pos?: { line: number; column: number }) { - if (typeof warning === 'string') warning = { message: warning }; - warning = augmentCodeLocation({ - object: warning, - pos, - code: 'PLUGIN_WARNING', - id, - source, - pluginName: plugin.name || '(anonymous plugin)' - }); - graph.warn(warning); - }, - error(err: RollupError | string, pos?: { line: number; column: number }) { - if (typeof err === 'string') err = { message: err }; - err = augmentCodeLocation({ - object: err, - pos, - code: 'PLUGIN_ERROR', - id, - source, - pluginName: plugin.name || '(anonymous plugin)' - }); - error(err); - }, - emitAsset, - setAssetSource: () => - error({ - code: 'INVALID_SETASSETSOURCE', - message: `setAssetSource cannot be called in transform for caching reasons. Use emitAsset with a source, or call setAssetSource in another hook.` - }) - }; -} - export default function transform( graph: Graph, source: TransformSourceDescription, - module: Module, - plugins: Plugin[] + module: Module ) { const id = module.id; const sourcemapChain: RawSourceMap[] = []; const originalSourcemap = typeof source.map === 'string' ? JSON.parse(source.map) : source.map; - - if (originalSourcemap && typeof originalSourcemap.mappings === 'string') { + if (originalSourcemap && typeof originalSourcemap.mappings === 'string') originalSourcemap.mappings = decode(originalSourcemap.mappings); - } - const baseEmitAsset = graph.pluginContext.emitAsset; + const baseEmitAsset = graph.pluginDriver.emitAsset; const originalCode = source.code; let ast = source.ast; - let promise = Promise.resolve(source.code); let transformDependencies: string[]; - plugins.forEach(plugin => { - if (!plugin.transform) return; - - promise = promise.then(previous => { - const { assets, emitAsset } = createTransformEmitAsset(graph.assetsById, baseEmitAsset); - return Promise.resolve() - .then(() => { - const context = createPluginTransformContext(graph, plugin, id, previous, emitAsset); - return plugin.transform.call(context, previous, id); - }) - .then(result => { - // assets emitted by transform are transformDependencies - if (assets.length) module.transformAssets = assets; - for (const asset of assets) { - if (asset.dependencies) { - for (const depId of asset.dependencies) { - if (!transformDependencies) transformDependencies = []; - if (transformDependencies.indexOf(depId) === -1) transformDependencies.push(depId); - } - } - } + let assets: Asset[]; + const curSource: string = source.code; + + function transformReducer(code: string, result: any, plugin: Plugin) { + // assets emitted by transform are transformDependencies + if (assets.length) module.transformAssets = assets; + for (const asset of assets) { + if (asset.dependencies) { + for (const depId of asset.dependencies) { + if (!transformDependencies) transformDependencies = []; + if (transformDependencies.indexOf(depId) === -1) transformDependencies.push(depId); + } + } + } - if (result == null) return previous; + if (result == null) return code; + + if (typeof result === 'string') { + result = { + code: result, + ast: undefined, + map: undefined + }; + } else if (typeof result.map === 'string') { + // `result.map` can only be a string if `result` isn't + result.map = JSON.parse(result.map); + } - if (typeof result === 'string') { - result = { - code: result, - ast: undefined, - map: undefined - }; - } else if (typeof result.map === 'string') { - // `result.map` can only be a string if `result` isn't - result.map = JSON.parse(result.map); - } + if (Array.isArray(result.dependencies)) { + if (!transformDependencies) transformDependencies = []; + for (const dep of result.dependencies) { + transformDependencies.push(resolve(dirname(id), dep)); + } + } - if (Array.isArray(result.dependencies)) { - if (!transformDependencies) transformDependencies = []; - for (const dep of result.dependencies) { - transformDependencies.push(resolve(dirname(id), dep)); - } - } + if (result.map && typeof result.map.mappings === 'string') { + result.map.mappings = decode(result.map.mappings); + } - if (result.map && typeof result.map.mappings === 'string') { - result.map.mappings = decode(result.map.mappings); - } + // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning + if (result.map !== null) { + sourcemapChain.push(result.map || { missing: true, plugin: plugin.name }); + } - // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning - if (result.map !== null) { - sourcemapChain.push(result.map || { missing: true, plugin: plugin.name }); - } + ast = result.ast; - ast = result.ast; + return result.code; + } - return result.code; - }) - .catch(err => { - if (typeof err === 'string') err = { message: err }; - if (err.code !== 'PLUGIN_ERROR') { + return graph.pluginDriver + .hookReduceArg0( + 'transform', + [curSource, id], + transformReducer, + (pluginContext, plugin) => { + let emitAsset: EmitAsset; + ({ assets, emitAsset } = createTransformEmitAsset(graph.assetsById, baseEmitAsset)); + return { + ...pluginContext, + warn(warning: RollupWarning | string, pos?: { line: number; column: number }) { + if (typeof warning === 'string') warning = { message: warning }; + if (pos) augmentCodeLocation(warning, pos, curSource, id); + if (warning.code) warning.pluginCode = warning.code; + warning.id = id; + warning.code = 'PLUGIN_WARNING'; + warning.plugin = plugin.name || '(anonymous plugin)'; + warning.hook = 'transform'; + graph.warn(warning); + }, + error(err: RollupError | string, pos?: { line: number; column: number }) { + if (typeof err === 'string') err = { message: err }; + if (pos) augmentCodeLocation(err, pos, curSource, id); if (err.code) err.pluginCode = err.code; + err.id = id; err.code = 'PLUGIN_ERROR'; - } - err.plugin = plugin.name; - err.id = id; - error(err); - }); - }); - }); - - return promise.then(code => { - return { + err.plugin = plugin.name || '(anonymous plugin)'; + err.hook = 'transform'; + error(err); + }, + emitAsset, + setAssetSource: () => + error({ + code: 'INVALID_SETASSETSOURCE', + message: `setAssetSource cannot be called in transform for caching reasons. Use emitAsset with a source, or call setAssetSource in another hook.` + }) + }; + } + ) + .catch(err => { + if (typeof err === 'string') err = { message: err }; + if (err.code !== 'PLUGIN_ERROR') { + if (err.code) err.pluginCode = err.code; + err.code = 'PLUGIN_ERROR'; + } + err.id = id; + error(err); + }) + .then(code => ({ code, transformDependencies, originalCode, originalSourcemap, ast: ast, sourcemapChain - }; - }); + })); } diff --git a/src/utils/transformChunk.ts b/src/utils/transformChunk.ts index 0f22035c899..9ec8206175d 100644 --- a/src/utils/transformChunk.ts +++ b/src/utils/transformChunk.ts @@ -12,62 +12,66 @@ export default function transformChunk( sourcemapChain: RawSourceMap[], options: OutputOptions ) { - const transformChunkContext = { - ...graph.pluginContext, - ...createAssetPluginHooks(graph.assetsById) - }; + const transformChunkAssetPluginHooks = createAssetPluginHooks(graph.assetsById); - return graph.plugins.reduce((promise, plugin) => { - if (!plugin.transformBundle && !plugin.transformChunk) return promise; + const transformChunkReducer = (code: string, result: any, plugin: Plugin): string => { + if (result == null) return code; - return promise.then(code => { - return Promise.resolve() - .then(() => - (plugin.transformChunk || plugin.transformBundle).call( - transformChunkContext, - code, - options, - chunk - ) - ) - .then(result => { - if (result == null) return code; + if (typeof result === 'string') { + result = { + code: result, + map: undefined + }; + } else if (!inTransformBundle && !result.map && options.sourcemap) { + throw new Error( + `${ + inTransformBundle ? 'transformBundle' : 'transformChunk' + } must return a "map" sourcemap property when sourcemaps are enabled.` + ); + } - if (plugin.transformBundle) { - if (typeof result === 'string') { - result = { - code: result, - map: undefined - }; - } - } else if (typeof result === 'string') { - throw new Error('transformChunk must return a { code, map } object, not a string.'); - } else if (!result.map && options.sourcemap) { - throw new Error( - 'transformChunk must return a "map" sourcemap property when sourcemaps are enabled.' - ); - } + const map = typeof result.map === 'string' ? JSON.parse(result.map) : result.map; + if (map && typeof map.mappings === 'string') { + map.mappings = decode(map.mappings); + } - const map = typeof result.map === 'string' ? JSON.parse(result.map) : result.map; - if (map && typeof map.mappings === 'string') { - map.mappings = decode(map.mappings); - } + // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning + if (map !== null) { + sourcemapChain.push(map || { missing: true, plugin: plugin.name }); + } - // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning - if (map !== null) { - sourcemapChain.push(map || { missing: true, plugin: plugin.name }); - } + return result.code; + }; - return result.code; + let inTransformBundle = false; + return graph.pluginDriver + .hookReduceArg0( + 'transformChunk', + [code, options, chunk], + transformChunkReducer, + pluginContext => ({ + ...pluginContext, + ...transformChunkAssetPluginHooks + }) + ) + .then(code => { + inTransformBundle = true; + return graph.pluginDriver.hookReduceArg0( + 'transformBundle', + [code, options, chunk], + transformChunkReducer, + pluginContext => ({ + ...pluginContext, + ...transformChunkAssetPluginHooks }) - .catch(err => { - error({ - code: plugin.transformChunk ? 'BAD_CHUNK_TRANSFORMER' : 'BAD_BUNDLE_TRANSFORMER', - message: `Error transforming ${(plugin.transformChunk ? 'chunk' : 'bundle') + - (plugin.name ? ` with '${plugin.name}' plugin` : '')}: ${err.message}`, - plugin: plugin.name - }); - }); + ); + }) + .catch(err => { + error(err, { + code: inTransformBundle ? 'BAD_BUNDLE_TRANSFORMER' : 'BAD_CHUNK_TRANSFORMER', + message: `Error transforming ${(inTransformBundle ? 'bundle' : 'chunk') + + (err.plugin ? ` with '${err.plugin}' plugin` : '')}: ${err.message}`, + plugin: err.plugin + }); }); - }, Promise.resolve(code)); } diff --git a/src/watch/index.ts b/src/watch/index.ts index e12d982b7fc..a17e00c3b2f 100644 --- a/src/watch/index.ts +++ b/src/watch/index.ts @@ -12,9 +12,7 @@ import { RollupSingleFileBuild, RollupWatchOptions } from '../rollup/types'; -import ensureArray from '../utils/ensureArray'; import mergeOptions from '../utils/mergeOptions'; -import { mapSequence } from '../utils/promise'; import chokidar from './chokidar'; import { addTask, deleteTask } from './fileWatchers'; @@ -27,9 +25,10 @@ export class Watcher extends EventEmitter { private tasks: Task[]; private succeeded: boolean = false; - constructor(configs: RollupWatchOptions[]) { + constructor(configs: RollupWatchOptions[] = []) { super(); - this.tasks = ensureArray(configs).map(config => new Task(this, config)); + if (!Array.isArray(configs)) configs = [configs]; + this.tasks = configs.map(config => new Task(this, config)); this.running = true; process.nextTick(() => this.run()); } @@ -64,7 +63,9 @@ export class Watcher extends EventEmitter { code: 'START' }); - mapSequence(this.tasks, (task: Task) => task.run()) + let taskPromise = Promise.resolve(); + for (const task of this.tasks) taskPromise = taskPromise.then(() => task.run()); + return taskPromise .then(() => { this.succeeded = true; this.running = false; diff --git a/test/function/samples/custom-path-resolver-plural-b/_config.js b/test/function/samples/custom-path-resolver-plural-b/_config.js index c4309622bb7..b063e5bc618 100644 --- a/test/function/samples/custom-path-resolver-plural-b/_config.js +++ b/test/function/samples/custom-path-resolver-plural-b/_config.js @@ -18,6 +18,9 @@ module.exports = { ] }, error: { - message: 'nope' + code: 'PLUGIN_ERROR', + hook: 'resolveId', + message: 'nope', + plugin: 'Plugin at pos 0' } }; diff --git a/test/function/samples/external-function-always-true/_config.js b/test/function/samples/external-function-always-true/_config.js index fc0d8e03692..36c5a9373b5 100644 --- a/test/function/samples/external-function-always-true/_config.js +++ b/test/function/samples/external-function-always-true/_config.js @@ -1,10 +1,10 @@ module.exports = { - description: 'prints useful error if external returns true for entry (#1264)', + description: 'Does not call external for entry point', options: { - external: id => true - }, - error: { - code: 'UNRESOLVED_ENTRY', - message: 'Entry module cannot be external' + external (id, parentId, isResolved) { + if (!parentId) + throw new Error('Should not call external for entry point.'); + return true; + } } }; diff --git a/test/function/samples/external-function-always-true/main.js b/test/function/samples/external-function-always-true/main.js index bfc7dca13a7..5fd1438b00d 100644 --- a/test/function/samples/external-function-always-true/main.js +++ b/test/function/samples/external-function-always-true/main.js @@ -1,2 +1,2 @@ import ext from 'external'; -assert.equal( ext, 42 ); +assert.equal( ext.external, true ); diff --git a/test/function/samples/plugin-error-loc-instead-pos/_config.js b/test/function/samples/plugin-error-loc-instead-pos/_config.js index b8799e6750c..84d37d2f370 100644 --- a/test/function/samples/plugin-error-loc-instead-pos/_config.js +++ b/test/function/samples/plugin-error-loc-instead-pos/_config.js @@ -16,6 +16,7 @@ module.exports = { code: 'PLUGIN_ERROR', plugin: 'test', message: 'nope', + hook: 'transform', id: path.resolve(__dirname, 'main.js'), loc: { file: path.resolve(__dirname, 'main.js'), diff --git a/test/function/samples/plugin-error-only-first-transform-bundle/_config.js b/test/function/samples/plugin-error-only-first-transform-bundle/_config.js index 406ade62979..e5dce3138aa 100644 --- a/test/function/samples/plugin-error-only-first-transform-bundle/_config.js +++ b/test/function/samples/plugin-error-only-first-transform-bundle/_config.js @@ -19,6 +19,7 @@ module.exports = { generateError: { code: 'BAD_BUNDLE_TRANSFORMER', plugin: 'plugin1', + hook: 'transformBundle', message: `Error transforming bundle with 'plugin1' plugin: Something happened 1` } }; diff --git a/test/function/samples/plugin-error-only-first-transform/_config.js b/test/function/samples/plugin-error-only-first-transform/_config.js index 057949f1321..49240cfbde5 100644 --- a/test/function/samples/plugin-error-only-first-transform/_config.js +++ b/test/function/samples/plugin-error-only-first-transform/_config.js @@ -22,6 +22,7 @@ module.exports = { code: 'PLUGIN_ERROR', message: `Something happened 1`, plugin: 'plugin1', + hook: 'transform', id: path.resolve(__dirname, 'main.js') } }; diff --git a/test/function/samples/plugin-error/_config.js b/test/function/samples/plugin-error/_config.js index 2b7ddea8103..5cfd7e38358 100644 --- a/test/function/samples/plugin-error/_config.js +++ b/test/function/samples/plugin-error/_config.js @@ -16,6 +16,7 @@ module.exports = { code: 'PLUGIN_ERROR', plugin: 'test', message: 'nope', + hook: 'transform', id: path.resolve(__dirname, 'main.js'), pos: 22, loc: { diff --git a/test/function/samples/plugin-warn-loc-instead-pos/_config.js b/test/function/samples/plugin-warn-loc-instead-pos/_config.js index d2c0e9e0342..995ed6d8499 100644 --- a/test/function/samples/plugin-warn-loc-instead-pos/_config.js +++ b/test/function/samples/plugin-warn-loc-instead-pos/_config.js @@ -18,6 +18,7 @@ module.exports = { code: 'PLUGIN_WARNING', id: path.resolve(__dirname, 'main.js'), plugin: 'test', + hook: 'transform', message: 'foo', loc: { file: path.resolve(__dirname, 'main.js'), diff --git a/test/function/samples/plugin-warn/_config.js b/test/function/samples/plugin-warn/_config.js index d7dee5027ad..80cf24b78d6 100644 --- a/test/function/samples/plugin-warn/_config.js +++ b/test/function/samples/plugin-warn/_config.js @@ -18,6 +18,7 @@ module.exports = { { code: 'PLUGIN_WARNING', id: path.resolve(__dirname, 'main.js'), + hook: 'transform', plugin: 'test', message: 'foo' }, @@ -25,6 +26,7 @@ module.exports = { code: 'PLUGIN_WARNING', id: path.resolve(__dirname, 'main.js'), plugin: 'test', + hook: 'transform', message: 'bar', pos: 22, loc: { diff --git a/test/sourcemaps/samples/transform-without-sourcemap/_config.js b/test/sourcemaps/samples/transform-without-sourcemap/_config.js index 97501a41f47..91056715a8f 100644 --- a/test/sourcemaps/samples/transform-without-sourcemap/_config.js +++ b/test/sourcemaps/samples/transform-without-sourcemap/_config.js @@ -3,13 +3,13 @@ module.exports = { options: { plugins: [ { - name: 'fake plugin', + name: 'fake plugin 1', transform(code) { return code; } }, { - name: 'fake plugin', + name: 'fake plugin 2', transform(code) { return { code, map: null }; }, @@ -22,8 +22,8 @@ module.exports = { warnings: [ { code: `SOURCEMAP_BROKEN`, - plugin: 'fake plugin', - message: `Sourcemap is likely to be incorrect: a plugin ('fake plugin') was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`, + plugin: 'fake plugin 1', + message: `Sourcemap is likely to be incorrect: a plugin ('fake plugin 1') was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`, url: `https://github.com/rollup/rollup/wiki/Troubleshooting#sourcemap-is-likely-to-be-incorrect` } ]