Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Graph.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/// <reference path="./Graph.d.ts" />
import * as acorn from 'acorn';
import injectDynamicImportPlugin from './utils/dynamic-import-plugin';
import { timeEnd, timeStart } from './utils/flushTime';
import first from './utils/first';
import Module, { IdMap, ModuleJSON } from './Module';
Expand Down Expand Up @@ -49,6 +51,7 @@ function generateChunkName (id: string, chunkNames: { [name: string ]: boolean }

export default class Graph {
acornOptions: any;
acornParse: acorn.IParse;
cachedModules: Map<string, ModuleJSON>;
context: string;
dynamicImport: boolean;
Expand Down Expand Up @@ -165,17 +168,24 @@ export default class Graph {

this.varOrConst = options.preferConst ? 'const' : 'var';
this.legacy = options.legacy;

this.acornOptions = options.acorn || {};
const acornPluginsToInject = [];

this.dynamicImport = typeof options.experimentalDynamicImport === 'boolean' ? options.experimentalDynamicImport : false;

if (this.dynamicImport) {
this.resolveDynamicImport = first([
...this.plugins.map(plugin => plugin.resolveDynamicImport).filter(Boolean),
<ResolveDynamicImportHandler> ((specifier, parentId) => typeof specifier === 'string' && this.resolveId(specifier, parentId))
]);
acornPluginsToInject.push(injectDynamicImportPlugin);
this.acornOptions.plugins = this.acornOptions.plugins || {};
this.acornOptions.plugins.dynamicImport = true;
}

acornPluginsToInject.push(...ensureArray(options.acornInjectPlugins));
this.acornParse = acornPluginsToInject.reduce((acc, plugin) => plugin(acc), acorn).parse;
}

getPathRelativeToBaseDirname (resolvedId: string, parentId: string): string {
Expand Down
12 changes: 5 additions & 7 deletions src/Module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as acorn from 'acorn';
import wrapDynamicImportPlugin from './utils/dynamic-import-plugin';
import { IParse } from 'acorn';
import MagicString from 'magic-string';
import { locate } from 'locate-character';
import { timeStart, timeEnd } from './utils/flushTime';
Expand Down Expand Up @@ -40,8 +39,6 @@ import { isLiteral } from './ast/nodes/Literal';
import { missingExport } from './utils/defaults';
import Chunk from './Chunk';

wrapDynamicImportPlugin(acorn);

export interface IdMap { [key: string]: string; }

export interface CommentDescription {
Expand All @@ -63,9 +60,9 @@ export interface ReexportDescription {
module: Module;
}

function tryParse (module: Module, acornOptions: Object) {
function tryParse (module: Module, parse: IParse, acornOptions: Object) {
try {
return acorn.parse(module.code, Object.assign({
return parse(module.code, Object.assign({
ecmaVersion: 8,
sourceType: 'module',
onComment: (block: boolean, text: string, start: number, end: number) =>
Expand Down Expand Up @@ -192,7 +189,8 @@ export default class Module {
this.ast = clone(ast);
this.astClone = ast;
} else {
this.ast = <any>tryParse(this, graph.acornOptions); // TODO what happens to comments if AST is provided?
// TODO what happens to comments if AST is provided?
this.ast = <any>tryParse(this, graph.acornParse, graph.acornOptions);
this.astClone = clone(this.ast);
}

Expand Down
1 change: 1 addition & 0 deletions src/rollup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface InputOptions {
};

acorn?: {};
acornInjectPlugins?: Function[];
treeshake?: boolean | TreeshakingOptions;
context?: string;
moduleContext?: string | ((id: string) => string) | { [id: string]: string };
Expand Down
4 changes: 3 additions & 1 deletion src/utils/dynamic-import-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Dynamic Import support for acorn
import { PluginsObject, TokenType } from 'acorn';

export default function wrapDynamicImportPlugin (acorn: {
export default function injectDynamicImportPlugin (acorn: {
tokTypes: { [type: string]: TokenType },
plugins: PluginsObject
}) {
Expand Down Expand Up @@ -35,4 +35,6 @@ export default function wrapDynamicImportPlugin (acorn: {
};
});
};

return acorn;
}
1 change: 1 addition & 0 deletions src/utils/mergeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default function mergeOptions ({
legacy: getInputOption('legacy'),
treeshake: getObjectOption('treeshake'),
acorn: config.acorn,
acornInjectPlugins: config.acornInjectPlugins,
context: config.context,
moduleContext: config.moduleContext,
plugins: config.plugins,
Expand Down
2 changes: 1 addition & 1 deletion test/cli/samples/config-deprecations/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = {
warnings[1],
{
code: 'UNKNOWN_OPTION',
message: 'Unknown option found: abc. Allowed keys: input, legacy, treeshake, acorn, context, moduleContext, plugins, onwarn, watch, cache, preferConst, experimentalDynamicImport, experimentalCodeSplitting, entry, external, extend, amd, banner, footer, intro, format, outro, sourcemap, sourcemapFile, name, globals, interop, legacy, freeze, indent, strict, noConflict, paths, exports, file, dir, pureExternalModules'
message: 'Unknown option found: abc. Allowed keys: input, legacy, treeshake, acorn, acornInjectPlugins, context, moduleContext, plugins, onwarn, watch, cache, preferConst, experimentalDynamicImport, experimentalCodeSplitting, entry, external, extend, amd, banner, footer, intro, format, outro, sourcemap, sourcemapFile, name, globals, interop, legacy, freeze, indent, strict, noConflict, paths, exports, file, dir, pureExternalModules'
}

);
Expand Down
92 changes: 91 additions & 1 deletion test/misc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('sanity checks', () => {
warnings,
[{
code: 'UNKNOWN_OPTION',
message: 'Unknown option found: plUgins. Allowed keys: input, legacy, treeshake, acorn, context, moduleContext, plugins, onwarn, watch, cache, preferConst, experimentalDynamicImport, experimentalCodeSplitting, entry, external, extend, amd, banner, footer, intro, format, outro, sourcemap, sourcemapFile, name, globals, interop, legacy, freeze, indent, strict, noConflict, paths, exports, file, dir, pureExternalModules'
message: 'Unknown option found: plUgins. Allowed keys: input, legacy, treeshake, acorn, acornInjectPlugins, context, moduleContext, plugins, onwarn, watch, cache, preferConst, experimentalDynamicImport, experimentalCodeSplitting, entry, external, extend, amd, banner, footer, intro, format, outro, sourcemap, sourcemapFile, name, globals, interop, legacy, freeze, indent, strict, noConflict, paths, exports, file, dir, pureExternalModules'
}]
);
}
Expand Down Expand Up @@ -319,6 +319,96 @@ describe('bundle.write()', () => {
});
});

describe('acorn plugins', () => {
// Acorn registers plugins globally per process. The tests in this suite
// use unique plugin names to make sure each plugin is registered in its
// proper test rather than in a test that ran earlier.

it('injects plugins passed in acornInjectPlugins', () => {
let pluginAInjected = false;
let pluginBInjected = false;

return rollup.rollup({
input: 'x',
plugins: [loader({ x: `export default 42` })],
acornInjectPlugins: [
function pluginA(acorn) { pluginAInjected = true; return acorn; },
function pluginB(acorn) { pluginBInjected = true; return acorn; },
]
}).then(executeBundle).then(result => {
assert.equal(result, 42);
assert(pluginAInjected, 'A plugin passed via acornInjectPlugins should inject itself into Acorn.');
assert(pluginBInjected, 'A plugin passed via acornInjectPlugins should inject itself into Acorn.');
});
});

it('injected plugins are registered with Acorn only if acorn.plugins is set', () => {
let pluginCRegistered = false;
let pluginDRegistered = false;

function pluginC(acorn) {
acorn.plugins.pluginC = () => pluginCRegistered = true;
return acorn;
}

function pluginD(acorn) {
acorn.plugins.pluginD = () => pluginDRegistered = true;
return acorn;
}

return rollup.rollup({
input: 'x',
plugins: [loader({ x: `export default 42` })],
acorn: {
plugins: {
pluginC: true
}
},
acornInjectPlugins: [
pluginC,
pluginD
]
}).then(executeBundle).then(result => {
assert.equal(result, 42);
assert.equal(pluginCRegistered, true, 'A plugin enabled in acorn.plugins should register with Acorn.');
assert.equal(pluginDRegistered, false, 'A plugin not enabled in acorn.plugins should not register with Acorn.');
});
});

it('throws if acorn.plugins is set and acornInjectPlugins is missing', () => {
return rollup.rollup({
input: 'x',
plugins: [loader({ x: `export default 42` })],
acorn: {
plugins: {
pluginE: true
}
}
}).then(executeBundle).then(() => {
throw new Error('Missing expected error');
}).catch(error => {
assert.equal(error.message, 'Plugin \'pluginE\' not found');
});
});

it('throws if acorn.plugins is set and acornInjectPlugins is empty', () => {
return rollup.rollup({
input: 'x',
plugins: [loader({ x: `export default 42` })],
acorn: {
plugins: {
pluginF: true
}
},
acornInjectPlugins: []
}).then(executeBundle).then(() => {
throw new Error('Missing expected error');
}).catch(error => {
assert.equal(error.message, 'Plugin \'pluginF\' not found');
});
});
});

describe('misc', () => {
it('warns if node builtins are unresolved in a non-CJS, non-ES bundle (#1051)', () => {
const warnings = [];
Expand Down