Skip to content

Commit

Permalink
support loading ESM plugins from the CLI (#4265)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
  • Loading branch information
kzc and lukastaegert committed Nov 12, 2021
1 parent 2810269 commit 2d85884
Show file tree
Hide file tree
Showing 19 changed files with 151 additions and 20 deletions.
2 changes: 1 addition & 1 deletion build-plugins/esm-dynamic-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: ')' };
}
}
Expand Down
30 changes: 20 additions & 10 deletions cli/run/commandPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>
): void {
): Promise<void> {
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<void> {
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] === '{') {
Expand All @@ -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
Expand All @@ -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}.`);
}
Expand Down Expand Up @@ -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<any> {
try {
return require(pluginPath);
} catch {
return import(pluginPath);
}
}
2 changes: 1 addition & 1 deletion cli/run/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,5 @@ async function getConfigs(
const { options, warnings } = await loadAndParseConfigFile(configFile, command);
return { options, warnings };
}
return loadConfigFromCommand(command);
return await loadConfigFromCommand(command);
}
11 changes: 6 additions & 5 deletions cli/run/loadConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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`));
Expand Down
6 changes: 3 additions & 3 deletions cli/run/loadConfigFromCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import batchWarnings, { BatchWarnings } from './batchWarnings';
import { addCommandPluginsToInputOptions } from './commandPlugins';
import { stdinName } from './stdin';

export default function loadConfigFromCommand(command: Record<string, any>): {
export default async function loadConfigFromCommand(command: Record<string, any>): 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 };
}
6 changes: 6 additions & 0 deletions test/cli/samples/plugin/absolute-esm/_config.js
Original file line number Diff line number Diff line change
@@ -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'}"`
};
2 changes: 2 additions & 0 deletions test/cli/samples/plugin/absolute-esm/_expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Hello Absolute ESM
console.log(2 );
8 changes: 8 additions & 0 deletions test/cli/samples/plugin/absolute-esm/my-esm-plugin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function(options = {}) {
const {comment} = options;
return {
transform(code) {
return `// Hello ${comment}\n${code}`;
}
};
};
10 changes: 10 additions & 0 deletions test/cli/samples/plugin/advanced-esm/_config.js
Original file line number Diff line number Diff line change
@@ -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}}"`
};
20 changes: 20 additions & 0 deletions test/cli/samples/plugin/advanced-esm/_expected/cjs.js
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 18 additions & 0 deletions test/cli/samples/plugin/advanced-esm/_expected/es.js
Original file line number Diff line number Diff line change
@@ -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 };
4 changes: 4 additions & 0 deletions test/cli/samples/plugin/advanced-esm/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {Foo} from "foo";
var foo = new Foo(123);
foo.output();
export {Foo as Bar};
10 changes: 10 additions & 0 deletions test/cli/samples/plugin/advanced-esm/node_modules/foo/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions test/cli/samples/plugin/advanced-esm/rollup.config.js
Original file line number Diff line number Diff line change
@@ -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'
}
]
};
6 changes: 6 additions & 0 deletions test/cli/samples/plugin/relative-esm/_config.js
Original file line number Diff line number Diff line change
@@ -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'}"`
};
2 changes: 2 additions & 0 deletions test/cli/samples/plugin/relative-esm/_expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Hello Relative ESM
console.log(2 );

0 comments on commit 2d85884

Please sign in to comment.