Skip to content

Commit

Permalink
Support native ESM configs in Node 13, support untranspiled configs (#…
Browse files Browse the repository at this point in the history
…3445)

* Improve coverage

* Automatically discover .mjs and .cjs configs

* First draft for untranspiled config support

* Slightly change logic to see if this helps windows

* Start improving the documentation

* Add test for helpful error

* Remove console-group

* Refactor CLI to extract more useful loadConfigFile handler

* Add test for loadConfigFile

* Add documentation

* Keep watching the config file on initial errors

* Improve coverage

* Small fixes

* Improve coverage
  • Loading branch information
lukastaegert committed Mar 29, 2020
1 parent 618e7f6 commit 7eea04a
Show file tree
Hide file tree
Showing 102 changed files with 1,374 additions and 574 deletions.
20 changes: 18 additions & 2 deletions .circleci/config.yml
Expand Up @@ -31,16 +31,26 @@ jobs:
- run:
name: Running tests
command: npm run ci:test
node-v12-latest:
docker:
- image: rollupcabal/circleci-node-v12:latest
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: Running tests
command: npm run ci:test:only
- store_artifacts:
name: Storing browser build for REPL
path: /home/circleci/project/dist/rollup.browser.js
destination: rollup.browser.js
- run:
name: Post REPL comment
command: ./scripts/post-comment.js
node-v12-latest:
node-v13-latest:
docker:
- image: rollupcabal/circleci-node-v12:latest
- image: lukastaegert/circleci-node-v13:latest
steps:
- checkout
- restore_cache:
Expand Down Expand Up @@ -70,3 +80,9 @@ workflows:
filters:
tags:
only: /.*/
- node-v13-latest:
requires:
- analysis
filters:
tags:
only: /.*/
2 changes: 1 addition & 1 deletion .github/workflows/node-windows.yml
Expand Up @@ -8,7 +8,7 @@ jobs:

strategy:
matrix:
node: ['12', '10']
node: ['13', '12', '10']

name: ${{ matrix.node }} (Windows)
steps:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -13,7 +13,7 @@
* Do not strip `.js` extensions from AMD imports when the import is a user-supplied replacement for a non-resolvable dynamic import target (#3453)

### Pull Requests
* [#3449](https://github.com/rollup/rollup/pull/3449): Avoid the assumption of Buffer in browser envs (@lukastaegert)
* [#3449](https://github.com/rollup/rollup/pull/3449): Add hook to rewrite dynamic import expressions (@lukastaegert)
* [#3452](https://github.com/rollup/rollup/pull/3452): Avoid the assumption of Buffer in browser envs (@JoviDeCroock)
* [#3453](https://github.com/rollup/rollup/pull/3453): fix types since watch accepts single or array config (@lukeed)
* [#3456](https://github.com/rollup/rollup/pull/3456): fix SystemJS url in tutorial (@guybedford)
Expand Down
7 changes: 7 additions & 0 deletions build-plugins/conditional-fsevents-import.js
Expand Up @@ -4,10 +4,12 @@ const FSEVENTS_REQUIRE = "require('fsevents')";
const REPLACEMENT = "require('../../../src/watch/fsevents-importer').getFsEvents()";

export default function conditionalFsEventsImport() {
let transformed = false;
return {
name: 'conditional-fs-events-import',
transform(code, id) {
if (id.endsWith('fsevents-handler.js')) {
transformed = true;
const requireStatementPos = code.indexOf(FSEVENTS_REQUIRE);
if (requireStatementPos < 0) {
throw new Error(`Could not find expected fsevents import "${FSEVENTS_REQUIRE}"`);
Expand All @@ -20,6 +22,11 @@ export default function conditionalFsEventsImport() {
);
return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) };
}
},
buildEnd() {
if (!transformed) {
throw new Error('Could not find "fsevents-handler.js", was the file renamed?');
}
}
};
}
19 changes: 19 additions & 0 deletions build-plugins/esm-dynamic-import.js
@@ -0,0 +1,19 @@
export default function addBinShebangAndEsmImport() {
let importFound = false;
return {
name: 'esm-dynamic-import',
renderDynamicImport({ moduleId }) {
importFound = true;
if (moduleId.endsWith('loadConfigFile.ts')) {
return { left: 'import(', right: ')' };
}
},
generateBundle() {
if (!importFound) {
throw new Error(
'Could not find dynamic import in "loadConfigFile.ts", was the file renamed?'
);
}
}
};
}
23 changes: 0 additions & 23 deletions build-plugins/fix-acorn-esm-import.js

This file was deleted.

94 changes: 46 additions & 48 deletions cli/run/build.ts
@@ -1,23 +1,22 @@
import color from 'colorette';
import ms from 'pretty-ms';
import * as rollup from '../../src/node-entry';
import { InputOptions, OutputOptions, RollupBuild } from '../../src/rollup/types';
import { MergedRollupOptions } from '../../src/rollup/types';
import relativeId from '../../src/utils/relativeId';
import { handleError, stderr } from '../logging';
import SOURCEMAPPING_URL from '../sourceMappingUrl';
import { BatchWarnings } from './batchWarnings';
import { printTimings } from './timings';

export default function build(
inputOptions: InputOptions,
outputOptions: OutputOptions[],
export default async function build(
inputOptions: MergedRollupOptions,
warnings: BatchWarnings,
silent = false
): Promise<unknown> {
const outputOptions = inputOptions.output;
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') {
Expand All @@ -26,54 +25,53 @@ export default function build(
inputFiles = inputOptions.input.join(', ');
} else if (typeof inputOptions.input === 'object' && inputOptions.input !== null) {
inputFiles = Object.keys(inputOptions.input)
.map(name => (inputOptions.input as Record<string, string>)[name])
.map((name) => (inputOptions.input as Record<string, string>)[name])
.join(', ');
}
stderr(color.cyan(`\n${color.bold(inputFiles!)}${color.bold(files.join(', '))}...`));
}

return rollup
.rollup(inputOptions as any)
.then((bundle: RollupBuild) => {
if (useStdout) {
const output = outputOptions[0];
if (output.sourcemap && output.sourcemap !== 'inline') {
handleError({
code: 'MISSING_OUTPUT_OPTION',
message: 'You must specify a --file (-o) option when creating a file with a sourcemap'
});
}

return bundle.generate(output).then(({ output: outputs }) => {
for (const file of outputs) {
let source: string | Uint8Array;
if (file.type === 'asset') {
source = file.source;
} else {
source = file.code;
if (output.sourcemap === 'inline') {
source += `\n//# ${SOURCEMAPPING_URL}=${file.map!.toUrl()}\n`;
}
}
if (outputs.length > 1)
process.stdout.write('\n' + color.cyan(color.bold('//→ ' + file.fileName + ':')) + '\n');
process.stdout.write(source);
}
return null;
});
}
const bundle = await rollup.rollup(inputOptions as any);
if (useStdout) {
const output = outputOptions[0];
if (output.sourcemap && output.sourcemap !== 'inline') {
handleError({
code: 'ONLY_INLINE_SOURCEMAPS',
message: 'Only inline sourcemaps are supported when bundling to stdout.',
});
}

return Promise.all(outputOptions.map(output => bundle.write(output))).then(() => bundle);
})
.then((bundle: RollupBuild | null) => {
if (!silent) {
warnings.flush();
stderr(
color.green(`created ${color.bold(files.join(', '))} in ${color.bold(ms(Date.now() - start))}`)
);
if (bundle && bundle.getTimings) {
printTimings(bundle.getTimings());
const { output: outputs } = await bundle.generate(output);
for (const file of outputs) {
let source: string | Uint8Array;
if (file.type === 'asset') {
source = file.source;
} else {
source = file.code;
if (output.sourcemap === 'inline') {
source += `\n//# ${SOURCEMAPPING_URL}=${file.map!.toUrl()}\n`;
}
}
});
if (outputs.length > 1)
process.stdout.write(`\n${color.cyan(color.bold(`//→ ${file.fileName}:`))}\n`);
process.stdout.write(source);
}
if (!silent) {
warnings.flush();
}
return;
}

await Promise.all(outputOptions.map(bundle.write));
if (!silent) {
warnings.flush();
stderr(
color.green(
`created ${color.bold(files.join(', '))} in ${color.bold(ms(Date.now() - start))}`
)
);
if (bundle && bundle.getTimings) {
printTimings(bundle.getTimings());
}
}
}
74 changes: 74 additions & 0 deletions cli/run/commandPlugins.ts
@@ -0,0 +1,74 @@
import * as path from 'path';
import { InputOptions } from '../../src/rollup/types';
import { stdinPlugin } from './stdin';

export function addCommandPluginsToInputOptions(
inputOptions: InputOptions,
command: any
) {
if (command.stdin !== false) {
inputOptions.plugins!.push(stdinPlugin());
}
const commandPlugin = command.plugin;
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);
} else {
// split out plugins joined by commas
// -p node-resolve,commonjs,buble
plugin.split(',').forEach((plugin: string) => loadAndRegisterPlugin(inputOptions, plugin));
}
}
}
}

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 (!plugin) {
try {
if (pluginText[0] == '.') pluginText = path.resolve(pluginText);
plugin = require(pluginText);
} catch (ex) {
throw new Error(`Cannot load plugin "${pluginText}"`);
}
}
}
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
);
}
41 changes: 41 additions & 0 deletions cli/run/getConfigPath.ts
@@ -0,0 +1,41 @@
import { readdirSync, realpathSync } from 'fs';
import * as path from 'path';
import relative from 'require-relative';
import { handleError } from '../logging';

const DEFAULT_CONFIG_BASE = 'rollup.config';

export function getConfigPath(commandConfig: any): string {
const cwd = process.cwd();
if (commandConfig === true) {
return path.join(cwd, findConfigFileNameInCwd());
}
if (commandConfig.slice(0, 5) === 'node:') {
const pkgName = commandConfig.slice(5);
try {
return relative.resolve(`rollup-config-${pkgName}`, cwd);
} catch (err) {
try {
return relative.resolve(pkgName, cwd);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
handleError({
code: 'MISSING_EXTERNAL_CONFIG',
message: `Could not resolve config file "${commandConfig}"`
});
}
throw err;
}
}
}
return realpathSync(commandConfig);
}

function findConfigFileNameInCwd(): string {
const filesInWorkingDir = new Set(readdirSync(process.cwd()));
for (const extension of ['mjs', 'cjs']) {
const fileName = `${DEFAULT_CONFIG_BASE}.${extension}`;
if (filesInWorkingDir.has(fileName)) return fileName;
}
return `${DEFAULT_CONFIG_BASE}.js`;
}

0 comments on commit 7eea04a

Please sign in to comment.