diff --git a/cli/help.md b/cli/help.md index 0e7520d1ae2..f4aaf073ab5 100644 --- a/cli/help.md +++ b/cli/help.md @@ -16,6 +16,7 @@ Basic options: -m, --sourcemap Generate sourcemap (`-m inline` for inline map) -n, --name Name for UMD export -o, --file Single output file (if absent, prints to stdout) +-p, --plugin Use the plugin specified (may be repeated) -v, --version Show version number -w, --watch Watch files in bundle and rebuild on changes --amd.id ID for AMD module (default is anonymous) diff --git a/cli/run/build.ts b/cli/run/build.ts index 8182b9cce72..bb339882c73 100644 --- a/cli/run/build.ts +++ b/cli/run/build.ts @@ -13,13 +13,11 @@ export default function build( outputOptions: OutputOptions[], warnings: BatchWarnings, silent = false -) { +): Promise { const useStdout = !outputOptions[0].file && !outputOptions[0].dir; const start = Date.now(); - const files = useStdout - ? ['stdout'] - : outputOptions.map(t => relativeId(t.file || t.dir!)); + const files = useStdout ? ['stdout'] : outputOptions.map(t => relativeId(t.file || t.dir!)); if (!silent) { let inputFiles: string | undefined; if (typeof inputOptions.input === 'string') { @@ -61,13 +59,11 @@ export default function build( process.stdout.write('\n' + tc.cyan(tc.bold('//→ ' + file.fileName + ':')) + '\n'); process.stdout.write(source); } - return null + return null; }); } - return Promise.all(outputOptions.map(output => bundle.write(output))).then( - () => bundle - ); + return Promise.all(outputOptions.map(output => bundle.write(output))).then(() => bundle); }) .then((bundle: RollupBuild | null) => { if (!silent) { @@ -79,9 +75,5 @@ export default function build( printTimings(bundle.getTimings()); } } - }) - .catch((err: Error) => { - warnings.flush(); - handleError(err); }); } diff --git a/cli/run/index.ts b/cli/run/index.ts index d44a32a101b..7316f8ed471 100644 --- a/cli/run/index.ts +++ b/cli/run/index.ts @@ -1,6 +1,7 @@ import { realpathSync } from 'fs'; +import * as path from 'path'; import relative from 'require-relative'; -import { WarningHandler } from '../../src/rollup/types'; +import { InputOptions, WarningHandler } from '../../src/rollup/types'; import mergeOptions, { GenericConfigObject } from '../../src/utils/mergeOptions'; import { getAliasName } from '../../src/utils/relativeId'; import { handleError } from '../logging'; @@ -107,18 +108,86 @@ async function execute( } else { for (const config of configs) { const warnings = batchWarnings(); - const { inputOptions, outputOptions, optionError } = mergeOptions({ - command, - config, - defaultOnWarnHandler: warnings.add - }); - if (optionError) { - (inputOptions.onwarn as WarningHandler)({ code: 'UNKNOWN_OPTION', message: optionError }); + try { + const { inputOptions, outputOptions, optionError } = mergeOptions({ + command, + config, + defaultOnWarnHandler: warnings.add + }); + if (optionError) { + (inputOptions.onwarn as WarningHandler)({ code: 'UNKNOWN_OPTION', message: optionError }); + } + if (command.stdin !== false) { + inputOptions.plugins!.push(stdinPlugin()); + } + if (command.plugin) { + const plugins = Array.isArray(command.plugin) ? command.plugin : [command.plugin]; + for (const plugin of plugins) { + if (/[={}]/.test(plugin)) { + // -p plugin=value + // -p "{transform(c,i){...}}" + loadAndRegisterPlugin(inputOptions, plugin); + } else { + // split out plugins joined by commas + // -p node-resolve,commonjs,buble + plugin + .split(',') + .forEach((plugin: string) => loadAndRegisterPlugin(inputOptions, plugin)); + } + } + } + await build(inputOptions, outputOptions, warnings, command.silent); + } catch (err) { + warnings.flush(); + handleError(err); + } + } + } +} + +function loadAndRegisterPlugin(inputOptions: InputOptions, pluginText: string) { + let plugin: any = null; + let pluginArg: any = undefined; + if (pluginText[0] === '{') { + // -p "{transform(c,i){...}}" + plugin = new Function('return ' + pluginText); + } else { + const match = pluginText.match(/^([@.\/\\\w|^{}|-]+)(=(.*))?$/); + if (match) { + // -p plugin + // -p plugin=arg + pluginText = match[1]; + pluginArg = new Function('return ' + match[3])(); + } else { + throw new Error(`Invalid --plugin argument format: ${JSON.stringify(pluginText)}`); + } + if (!/^\.|^rollup-plugin-|[@\/\\]/.test(pluginText)) { + // Try using plugin prefix variations first if applicable. + // Prefix order is significant - left has higher precedence. + for (const prefix of ['@rollup/plugin-', 'rollup-plugin-']) { + try { + plugin = require(prefix + pluginText); + break; + } catch (ex) { + // if this does not work, we try requiring the actual name below + } } - if (command.stdin !== false) { - inputOptions.plugins!.push(stdinPlugin()); + } + if (!plugin) { + try { + if (pluginText[0] == '.') pluginText = path.resolve(pluginText); + plugin = require(pluginText); + } catch (ex) { + throw new Error(`Cannot load plugin "${pluginText}"`); } - await build(inputOptions, outputOptions, warnings, command.silent); } } + if (typeof plugin === 'object' && pluginText in plugin) { + // some plugins do not use `export default` for their entry point. + // attempt to use the plugin name as the named import name. + plugin = plugin[pluginText]; + } + inputOptions.plugins!.push( + typeof plugin === 'function' ? plugin.call(plugin, pluginArg) : plugin + ); } diff --git a/cli/run/loadConfigFile.ts b/cli/run/loadConfigFile.ts index da7a911fc95..e506150c3a3 100644 --- a/cli/run/loadConfigFile.ts +++ b/cli/run/loadConfigFile.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; import tc from 'turbocolor'; import * as rollup from '../../src/node-entry'; import { RollupBuild, RollupOutput } from '../../src/rollup/types'; @@ -73,4 +73,4 @@ export default function loadConfigFile( return Array.isArray(configs) ? configs : [configs]; }); }); -} \ No newline at end of file +} diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index 1cb74a4a9ac..b2aa9f0c4f5 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -220,6 +220,7 @@ Many options have command line equivalents. In those cases, any arguments passed -m, --sourcemap Generate sourcemap (`-m inline` for inline map) -n, --name Name for UMD export -o, --file Single output file (if absent, prints to stdout) +-p, --plugin Use the plugin specified (may be repeated) -v, --version Show version number -w, --watch Watch files in bundle and rebuild on changes --amd.id ID for AMD module (default is anonymous) @@ -268,6 +269,47 @@ The flags listed below are only available via the command line interface. All ot Print the help document. +#### `-p `, `--plugin ` + +Use the specified plugin. There are several ways to specify plugins here: + +- Via a relative path: + + ``` + rollup -i input.js -f es -p ./my-plugin.js + ``` + + The file should export a plugin object or a function returning such an object. +- Via the name of a plugin that is installed in a local or global `node_modules` folder: + + ``` + rollup -i input.js -f es -p @rollup/plugin-node-resolve + ``` + + If the plugin name does not start with `rollup-plugin-` or `@rollup/plugin-`, Rollup will automatically try adding these prefixes: + + ``` + rollup -i input.js -f es -p node-resolve + ``` + +- Via an inline implementation: + + ``` + rollup -i input.js -f es -p '{transform: (c, i) => `/* ${JSON.stringify(i)} */\n${c}`}' + ``` + +If you want to load more than one plugin, you can repeat the option or supply a comma-separated list of names: + +``` +rollup -i input.js -f es -p node-resolve -p commonjs,json +``` + +By default, plugins that export functions will be called with no argument to create the plugin. You can however pass a custom argument as well: + +``` +rollup -i input.js -f es -p 'terser={output: {beautify: true, indent_level: 2}}' +``` + #### `-v`/`--version` Print the installed version number. diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index 46f71f43d23..a8e5ae00c08 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -98,6 +98,7 @@ export const commandAliases: { [key: string]: string } = { m: 'sourcemap', n: 'name', o: 'file', + p: 'plugin', v: 'version', w: 'watch' }; @@ -158,6 +159,7 @@ export default function mergeOptions({ Object.keys(commandAliases), 'config', 'environment', + 'plugin', 'silent', 'stdin' ), diff --git a/test/cli/samples/plugin/absolute/_config.js b/test/cli/samples/plugin/absolute/_config.js new file mode 100644 index 00000000000..9f8cebd1f00 --- /dev/null +++ b/test/cli/samples/plugin/absolute/_config.js @@ -0,0 +1,5 @@ +module.exports = { + description: 'CLI --plugin /absolute/path', + skipIfWindows: true, + command: `echo 'console.log(VALUE);' | rollup -p "\`pwd\`/my-plugin={VALUE: 'absolute', ZZZ: 1}"` +}; diff --git a/test/cli/samples/plugin/absolute/_expected.js b/test/cli/samples/plugin/absolute/_expected.js new file mode 100644 index 00000000000..3a3206b062c --- /dev/null +++ b/test/cli/samples/plugin/absolute/_expected.js @@ -0,0 +1 @@ +console.log("absolute"); diff --git a/test/cli/samples/plugin/absolute/my-plugin.js b/test/cli/samples/plugin/absolute/my-plugin.js new file mode 100644 index 00000000000..800515033ef --- /dev/null +++ b/test/cli/samples/plugin/absolute/my-plugin.js @@ -0,0 +1,14 @@ +module.exports = function(options) { + if (options === void 0) options = {}; + return { + transform(code) { + // dumb search and replace for test purposes + for (var key in options) { + const rx = new RegExp(key, 'g'); + const value = JSON.stringify(options[key]); + code = code.replace(rx, value); + } + return code; + } + }; +}; diff --git a/test/cli/samples/plugin/advanced/_config.js b/test/cli/samples/plugin/advanced/_config.js new file mode 100644 index 00000000000..8028fd603e7 --- /dev/null +++ b/test/cli/samples/plugin/advanced/_config.js @@ -0,0 +1,5 @@ +module.exports = { + description: 'advanced CLI --plugin functionality with rollup config', + skipIfWindows: true, + command: `rollup -c -p node-resolve,commonjs -p "terser={output: {beautify: true, indent_level: 2}}"` +}; diff --git a/test/cli/samples/plugin/advanced/_expected/cjs.js b/test/cli/samples/plugin/advanced/_expected/cjs.js new file mode 100644 index 00000000000..7cbd6c70f5f --- /dev/null +++ b/test/cli/samples/plugin/advanced/_expected/cjs.js @@ -0,0 +1,17 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: !0 +}); + +var t = function() { + function t(t) { + this.x = t; + } + return t.prototype.output = function() { + var t; + t = this.x, console.log(t); + }, t; +}(); + +new t(123).output(), exports.Bar = t; diff --git a/test/cli/samples/plugin/advanced/_expected/es.js b/test/cli/samples/plugin/advanced/_expected/es.js new file mode 100644 index 00000000000..6b0e9e7249a --- /dev/null +++ b/test/cli/samples/plugin/advanced/_expected/es.js @@ -0,0 +1,13 @@ +var t = function() { + function t(t) { + this.x = t; + } + return t.prototype.output = function() { + var t; + t = this.x, console.log(t); + }, t; +}(); + +new t(123).output(); + +export { t as Bar }; diff --git a/test/cli/samples/plugin/advanced/main.js b/test/cli/samples/plugin/advanced/main.js new file mode 100644 index 00000000000..e04e2255af9 --- /dev/null +++ b/test/cli/samples/plugin/advanced/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/node_modules/foo/index.js b/test/cli/samples/plugin/advanced/node_modules/foo/index.js new file mode 100644 index 00000000000..87e1bc97c38 --- /dev/null +++ b/test/cli/samples/plugin/advanced/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/node_modules/print/index.js b/test/cli/samples/plugin/advanced/node_modules/print/index.js new file mode 100644 index 00000000000..be89775f9d0 --- /dev/null +++ b/test/cli/samples/plugin/advanced/node_modules/print/index.js @@ -0,0 +1,3 @@ +module.exports = function(value) { + console.log(value); +}; diff --git a/test/cli/samples/plugin/advanced/rollup.config.js b/test/cli/samples/plugin/advanced/rollup.config.js new file mode 100644 index 00000000000..8cfd5b78416 --- /dev/null +++ b/test/cli/samples/plugin/advanced/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/basic/_config.js b/test/cli/samples/plugin/basic/_config.js new file mode 100644 index 00000000000..2cfebb4e7ec --- /dev/null +++ b/test/cli/samples/plugin/basic/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'basic CLI --plugin functionality', + command: `rollup main.js -f cjs --plugin rollup-plugin-buble` +}; diff --git a/test/cli/samples/plugin/basic/_expected.js b/test/cli/samples/plugin/basic/_expected.js new file mode 100644 index 00000000000..e83de861ed5 --- /dev/null +++ b/test/cli/samples/plugin/basic/_expected.js @@ -0,0 +1,14 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var Bar = function Bar(x) { + this.x = value; +}; +Bar.prototype.value = function value () { + return this.x; +}; +var bar = new Bar(123); +console.log(bar.value()); + +exports.Bar = Bar; diff --git a/test/cli/samples/plugin/basic/main.js b/test/cli/samples/plugin/basic/main.js new file mode 100644 index 00000000000..49486331fe7 --- /dev/null +++ b/test/cli/samples/plugin/basic/main.js @@ -0,0 +1,10 @@ +export class Bar { + constructor(x) { + this.x = value; + } + value() { + return this.x; + } +} +var bar = new Bar(123); +console.log(bar.value()); diff --git a/test/cli/samples/plugin/cannot-load/_config.js b/test/cli/samples/plugin/cannot-load/_config.js new file mode 100644 index 00000000000..dc942ccb073 --- /dev/null +++ b/test/cli/samples/plugin/cannot-load/_config.js @@ -0,0 +1,10 @@ +const { assertStderrIncludes } = require('../../../../utils.js'); + +module.exports = { + description: 'unknown CLI --plugin results in an error', + skipIfWindows: true, + command: `echo "console.log(123);" | rollup --plugin foobar`, + error(err) { + assertStderrIncludes(err.message, '[!] Error: Cannot load plugin "foobar"'); + } +}; diff --git a/test/cli/samples/plugin/invalid-argument/_config.js b/test/cli/samples/plugin/invalid-argument/_config.js new file mode 100644 index 00000000000..21f29a3e3d7 --- /dev/null +++ b/test/cli/samples/plugin/invalid-argument/_config.js @@ -0,0 +1,10 @@ +const { assertStderrIncludes } = require('../../../../utils.js'); + +module.exports = { + description: 'invalid CLI --plugin argument format', + skipIfWindows: true, + command: `echo "console.log(123);" | rollup --plugin 'foo bar'`, + error(err) { + assertStderrIncludes(err.message, '[!] Error: Invalid --plugin argument format: "foo bar"'); + } +}; diff --git a/test/cli/samples/plugin/object/_config.js b/test/cli/samples/plugin/object/_config.js new file mode 100644 index 00000000000..f102a47afeb --- /dev/null +++ b/test/cli/samples/plugin/object/_config.js @@ -0,0 +1,5 @@ +module.exports = { + description: 'CLI --plugin object', + skipIfWindows: true, + command: `echo 'console.log(42);' | rollup -f cjs -p '{transform: c => c + String.fromCharCode(10) + c}'` +}; diff --git a/test/cli/samples/plugin/object/_expected.js b/test/cli/samples/plugin/object/_expected.js new file mode 100644 index 00000000000..19a108d7e99 --- /dev/null +++ b/test/cli/samples/plugin/object/_expected.js @@ -0,0 +1,5 @@ +'use strict'; + +console.log(42); + +console.log(42); diff --git a/test/cli/samples/plugin/relative/_config.js b/test/cli/samples/plugin/relative/_config.js new file mode 100644 index 00000000000..177951ac68e --- /dev/null +++ b/test/cli/samples/plugin/relative/_config.js @@ -0,0 +1,5 @@ +module.exports = { + description: 'CLI --plugin ../relative/path', + skipIfWindows: true, + command: `echo 'console.log(VALUE);' | rollup -p "../absolute/my-plugin={VALUE: 'relative', ZZZ: 1}"` +}; diff --git a/test/cli/samples/plugin/relative/_expected.js b/test/cli/samples/plugin/relative/_expected.js new file mode 100644 index 00000000000..1059ebb529a --- /dev/null +++ b/test/cli/samples/plugin/relative/_expected.js @@ -0,0 +1 @@ +console.log("relative"); diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 9332c129202..3fab4a66075 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,3 +1,3 @@ exports.input = 'acorn, acornInjectPlugins, cache, chunkGroupingSize, context, experimentalCacheExpiry, experimentalOptimizeChunks, external, inlineDynamicImports, input, manualChunks, moduleContext, onwarn, perf, plugins, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; -exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, exports, extend, external, externalLiveBindings, f, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, treeshake, v, w, watch'; +exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, exports, extend, external, externalLiveBindings, f, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, treeshake, v, w, watch'; exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, hoistTransitiveImports, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict';