diff --git a/build-plugins/esm-dynamic-import.ts b/build-plugins/esm-dynamic-import.ts index 3a2cfc6f18c..025d17a3757 100644 --- a/build-plugins/esm-dynamic-import.ts +++ b/build-plugins/esm-dynamic-import.ts @@ -13,7 +13,7 @@ export default function addBinShebangAndEsmImport(): Plugin { name: 'esm-dynamic-import', renderDynamicImport({ moduleId }) { importFound = true; - if (moduleId.endsWith('loadConfigFile.ts')) { + if (moduleId.endsWith('commandPlugins.ts') || moduleId.endsWith('loadConfigFile.ts')) { return { left: 'import(', right: ')' }; } } diff --git a/cli/run/commandPlugins.ts b/cli/run/commandPlugins.ts index e4b2e5feaf6..df4bb31d662 100644 --- a/cli/run/commandPlugins.ts +++ b/cli/run/commandPlugins.ts @@ -3,40 +3,42 @@ import { InputOptions } from '../../src/rollup/types'; import { stdinPlugin } from './stdin'; import { waitForInputPlugin } from './waitForInput'; -export function addCommandPluginsToInputOptions( +export async function addCommandPluginsToInputOptions( inputOptions: InputOptions, command: Record -): void { +): Promise { if (command.stdin !== false) { inputOptions.plugins!.push(stdinPlugin(command.stdin)); } if (command.waitForBundleInput === true) { inputOptions.plugins!.push(waitForInputPlugin()); } - addPluginsFromCommandOption(command.plugin, inputOptions); + await addPluginsFromCommandOption(command.plugin, inputOptions); } -export function addPluginsFromCommandOption( +export async function addPluginsFromCommandOption( commandPlugin: unknown, inputOptions: InputOptions -): void { +): Promise { if (commandPlugin) { const plugins = Array.isArray(commandPlugin) ? commandPlugin : [commandPlugin]; for (const plugin of plugins) { if (/[={}]/.test(plugin)) { // -p plugin=value // -p "{transform(c,i){...}}" - loadAndRegisterPlugin(inputOptions, plugin); + await loadAndRegisterPlugin(inputOptions, plugin); } else { // split out plugins joined by commas // -p node-resolve,commonjs,buble - plugin.split(',').forEach((plugin: string) => loadAndRegisterPlugin(inputOptions, plugin)); + for (const p of plugin.split(',')) { + await loadAndRegisterPlugin(inputOptions, p); + } } } } } -function loadAndRegisterPlugin(inputOptions: InputOptions, pluginText: string): void { +async function loadAndRegisterPlugin(inputOptions: InputOptions, pluginText: string) { let plugin: any = null; let pluginArg: any = undefined; if (pluginText[0] === '{') { @@ -57,7 +59,7 @@ function loadAndRegisterPlugin(inputOptions: InputOptions, pluginText: string): // Prefix order is significant - left has higher precedence. for (const prefix of ['@rollup/plugin-', 'rollup-plugin-']) { try { - plugin = require(prefix + pluginText); + plugin = await requireOrImport(prefix + pluginText); break; } catch { // if this does not work, we try requiring the actual name below @@ -67,7 +69,7 @@ function loadAndRegisterPlugin(inputOptions: InputOptions, pluginText: string): if (!plugin) { try { if (pluginText[0] == '.') pluginText = path.resolve(pluginText); - plugin = require(pluginText); + plugin = await requireOrImport(pluginText); } catch (err: any) { throw new Error(`Cannot load plugin "${pluginText}": ${err.message}.`); } @@ -99,3 +101,11 @@ function getCamelizedPluginBaseName(pluginText: string): string { .map((part, index) => (index === 0 || !part ? part : part[0].toUpperCase() + part.slice(1))) .join(''); } + +async function requireOrImport(pluginPath: string): Promise { + try { + return require(pluginPath); + } catch { + return import(pluginPath); + } +} diff --git a/cli/run/index.ts b/cli/run/index.ts index 3618f5e567a..6ff743c79f8 100644 --- a/cli/run/index.ts +++ b/cli/run/index.ts @@ -92,5 +92,5 @@ async function getConfigs( const { options, warnings } = await loadAndParseConfigFile(configFile, command); return { options, warnings }; } - return loadConfigFromCommand(command); + return await loadConfigFromCommand(command); } diff --git a/cli/run/loadConfigFile.ts b/cli/run/loadConfigFile.ts index a0f292707fd..a973eea7434 100644 --- a/cli/run/loadConfigFile.ts +++ b/cli/run/loadConfigFile.ts @@ -27,11 +27,12 @@ export default async function loadAndParseConfigFile( const configs = await loadConfigFile(fileName, commandOptions); const warnings = batchWarnings(); try { - const normalizedConfigs = configs.map(config => { + const normalizedConfigs: MergedRollupOptions[] = []; + for (const config of configs) { const options = mergeOptions(config, commandOptions, warnings.add); - addCommandPluginsToInputOptions(options, commandOptions); - return options; - }); + await addCommandPluginsToInputOptions(options, commandOptions); + normalizedConfigs.push(options); + } return { options: normalizedConfigs, warnings }; } catch (err) { warnings.flush(); @@ -73,7 +74,7 @@ async function getDefaultFromTranspiledConfigFile( plugins: [], treeshake: false }; - addPluginsFromCommandOption(commandOptions.configPlugin, inputOptions); + await addPluginsFromCommandOption(commandOptions.configPlugin, inputOptions); const bundle = await rollup.rollup(inputOptions); if (!commandOptions.silent && warnings.count > 0) { stderr(bold(`loaded ${relativeId(fileName)} with warnings`)); diff --git a/cli/run/loadConfigFromCommand.ts b/cli/run/loadConfigFromCommand.ts index 806e70175c0..5f75caf6640 100644 --- a/cli/run/loadConfigFromCommand.ts +++ b/cli/run/loadConfigFromCommand.ts @@ -4,15 +4,15 @@ import batchWarnings, { BatchWarnings } from './batchWarnings'; import { addCommandPluginsToInputOptions } from './commandPlugins'; import { stdinName } from './stdin'; -export default function loadConfigFromCommand(command: Record): { +export default async function loadConfigFromCommand(command: Record): Promise<{ options: MergedRollupOptions[]; warnings: BatchWarnings; -} { +}> { const warnings = batchWarnings(); if (!command.input && (command.stdin || !process.stdin.isTTY)) { command.input = stdinName; } const options = mergeOptions({ input: [] }, command, warnings.add); - addCommandPluginsToInputOptions(options, command); + await addCommandPluginsToInputOptions(options, command); return { options: [options], warnings }; } diff --git a/test/cli/samples/plugin/absolute-esm/_config.js b/test/cli/samples/plugin/absolute-esm/_config.js new file mode 100644 index 00000000000..6c0c669e18a --- /dev/null +++ b/test/cli/samples/plugin/absolute-esm/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'ESM CLI --plugin /absolute/path', + minNodeVersion: 12, + skipIfWindows: true, + command: `echo 'console.log(1 ? 2 : 3);' | rollup -p "\`pwd\`/my-esm-plugin.mjs={comment: 'Absolute ESM'}"` +}; diff --git a/test/cli/samples/plugin/absolute-esm/_expected.js b/test/cli/samples/plugin/absolute-esm/_expected.js new file mode 100644 index 00000000000..4adbb01e16e --- /dev/null +++ b/test/cli/samples/plugin/absolute-esm/_expected.js @@ -0,0 +1,2 @@ +// Hello Absolute ESM +console.log(2 ); diff --git a/test/cli/samples/plugin/absolute-esm/my-esm-plugin.mjs b/test/cli/samples/plugin/absolute-esm/my-esm-plugin.mjs new file mode 100644 index 00000000000..dc135d072a9 --- /dev/null +++ b/test/cli/samples/plugin/absolute-esm/my-esm-plugin.mjs @@ -0,0 +1,8 @@ +export default function(options = {}) { + const {comment} = options; + return { + transform(code) { + return `// Hello ${comment}\n${code}`; + } + }; +}; diff --git a/test/cli/samples/plugin/advanced-esm/_config.js b/test/cli/samples/plugin/advanced-esm/_config.js new file mode 100644 index 00000000000..9dfaf7526cd --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/_config.js @@ -0,0 +1,10 @@ +module.exports = { + description: 'load an ESM-only rollup plugin from node_modules as well as CJS plugins', + minNodeVersion: 12, + skipIfWindows: true, + + // The NodeJS resolution rules for ESM modules are more restrictive than CJS. + // Must copy the ESM plugin into the main node_modules in order to use and test it. + + command: `rm -rf ../../../../../node_modules/rollup-plugin-esm-test && cp -rp node_modules/rollup-plugin-esm-test ../../../../../node_modules/ && rollup -c -p node-resolve,commonjs,esm-test -p "terser={mangle: false, output: {beautify: true, indent_level: 2}}"` +}; diff --git a/test/cli/samples/plugin/advanced-esm/_expected/cjs.js b/test/cli/samples/plugin/advanced-esm/_expected/cjs.js new file mode 100644 index 00000000000..a1e3d7e0f30 --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/_expected/cjs.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: !0 +}), console.log("esm-test: node_modules/print/index.js"); + +console.log("esm-test: node_modules/foo/index.js"); + +var print = function(value) { + console.log(value); +}, Foo = function() { + function Foo(x) { + this.x = x; + } + return Foo.prototype.output = function() { + print(this.x); + }, Foo; +}(); + +console.log("esm-test: main.js"), new Foo(123).output(), exports.Bar = Foo; diff --git a/test/cli/samples/plugin/advanced-esm/_expected/es.js b/test/cli/samples/plugin/advanced-esm/_expected/es.js new file mode 100644 index 00000000000..48421d8833a --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/_expected/es.js @@ -0,0 +1,18 @@ +console.log("esm-test: node_modules/print/index.js"); + +console.log("esm-test: node_modules/foo/index.js"); + +var print = function(value) { + console.log(value); +}, Foo = function() { + function Foo(x) { + this.x = x; + } + return Foo.prototype.output = function() { + print(this.x); + }, Foo; +}(); + +console.log("esm-test: main.js"), new Foo(123).output(); + +export { Foo as Bar }; diff --git a/test/cli/samples/plugin/advanced-esm/main.js b/test/cli/samples/plugin/advanced-esm/main.js new file mode 100644 index 00000000000..e04e2255af9 --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/main.js @@ -0,0 +1,4 @@ +import {Foo} from "foo"; +var foo = new Foo(123); +foo.output(); +export {Foo as Bar}; diff --git a/test/cli/samples/plugin/advanced-esm/node_modules/foo/index.js b/test/cli/samples/plugin/advanced-esm/node_modules/foo/index.js new file mode 100644 index 00000000000..87e1bc97c38 --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/node_modules/foo/index.js @@ -0,0 +1,10 @@ +var print = require('print'); + +exports.Foo = class { + constructor(x) { + this.x = x; + } + output() { + print(this.x); + } +}; diff --git a/test/cli/samples/plugin/advanced-esm/node_modules/print/index.js b/test/cli/samples/plugin/advanced-esm/node_modules/print/index.js new file mode 100644 index 00000000000..be89775f9d0 --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/node_modules/print/index.js @@ -0,0 +1,3 @@ +module.exports = function(value) { + console.log(value); +}; diff --git a/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/index.mjs b/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/index.mjs new file mode 100644 index 00000000000..14daca5000a --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/index.mjs @@ -0,0 +1,9 @@ +export function esmTest() { + return { + transform(code, id) { + if (id.includes("\0")) return null; + const name = JSON.stringify(id.replace(/^.*\/advanced-esm\//, "esm-test: ")); + return `console.log(${name});\n${code}`; + } + }; +} diff --git a/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/package.json b/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/package.json new file mode 100644 index 00000000000..5db8a5c8cd5 --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/node_modules/rollup-plugin-esm-test/package.json @@ -0,0 +1,4 @@ +{ + "type": "module", + "main": "./index.mjs" +} diff --git a/test/cli/samples/plugin/advanced-esm/rollup.config.js b/test/cli/samples/plugin/advanced-esm/rollup.config.js new file mode 100644 index 00000000000..f2db773934b --- /dev/null +++ b/test/cli/samples/plugin/advanced-esm/rollup.config.js @@ -0,0 +1,18 @@ +const buble = require('@rollup/plugin-buble'); + +export default { + input: 'main.js', + plugins: [ + buble() + ], + output: [ + { + file: '_actual/cjs.js', + format: 'cjs' + }, + { + file: '_actual/es.js', + format: 'esm' + } + ] +}; diff --git a/test/cli/samples/plugin/relative-esm/_config.js b/test/cli/samples/plugin/relative-esm/_config.js new file mode 100644 index 00000000000..2c0e40c1215 --- /dev/null +++ b/test/cli/samples/plugin/relative-esm/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'ESM CLI --plugin ../relative/path', + minNodeVersion: 12, + skipIfWindows: true, + command: `echo 'console.log(1 ? 2 : 3);' | rollup -p "../absolute-esm/my-esm-plugin.mjs={comment: 'Relative ESM'}"` +}; diff --git a/test/cli/samples/plugin/relative-esm/_expected.js b/test/cli/samples/plugin/relative-esm/_expected.js new file mode 100644 index 00000000000..1c1ba68f176 --- /dev/null +++ b/test/cli/samples/plugin/relative-esm/_expected.js @@ -0,0 +1,2 @@ +// Hello Relative ESM +console.log(2 );