Skip to content

Commit

Permalink
Always try to load config files directly if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Aug 26, 2022
1 parent 281f468 commit 96e8200
Show file tree
Hide file tree
Showing 98 changed files with 296 additions and 187 deletions.
29 changes: 0 additions & 29 deletions LICENSE.md
Expand Up @@ -240,35 +240,6 @@ Repository: jonschlinkert/fill-range
---------------------------------------

## get-package-type
License: MIT
By: Corey Farrell
Repository: git+https://github.com/cfware/get-package-type.git

> MIT License
>
> Copyright (c) 2020 CFWare, LLC
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
---------------------------------------

## glob-parent
License: ISC
By: Gulp Team, Elan Shanker, Blaine Bublitz
Expand Down
87 changes: 62 additions & 25 deletions cli/run/loadConfigFile.ts
@@ -1,11 +1,17 @@
import { promises as fs } from 'node:fs';
import { dirname, extname, isAbsolute, join } from 'node:path';
import { dirname, isAbsolute, join } from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
import getPackageType from 'get-package-type';
import * as rollup from '../../src/node-entry';
import type { MergedRollupOptions } from '../../src/rollup/types';
import { bold } from '../../src/utils/colors';
import { errMissingConfig, error } from '../../src/utils/error';
import {
errCannotBundleConfigAsEsm,
errCannotLoadConfigAsCjs,
errCannotLoadConfigAsEsm,
errMissingConfig,
error
} from '../../src/utils/error';
import { mergeOptions } from '../../src/utils/options/mergeOptions';
import type { GenericConfigObject } from '../../src/utils/options/options';
import relativeId from '../../src/utils/relativeId';
Expand All @@ -17,7 +23,10 @@ export async function loadConfigFile(
fileName: string,
commandOptions: any = {}
): Promise<{ options: MergedRollupOptions[]; warnings: BatchWarnings }> {
const configs = await loadConfigsFromFile(fileName, commandOptions);
const configs = await getConfigList(
getDefaultFromCjs(await getConfigFileExport(fileName, commandOptions)),
commandOptions
);
const warnings = batchWarnings();
try {
const normalizedConfigs: MergedRollupOptions[] = [];
Expand All @@ -33,20 +42,43 @@ export async function loadConfigFile(
}
}

async function loadConfigsFromFile(
fileName: string,
commandOptions: Record<string, unknown>
): Promise<GenericConfigObject[]> {
const extension = extname(fileName);

const configFileExport =
commandOptions.configPlugin ||
// We always transpile the .js non-module case because many legacy code bases rely on this
(extension === '.js' && (await getPackageType(fileName)) !== 'module')
? await loadTranspiledConfigFile(fileName, commandOptions)
: (await import(pathToFileURL(fileName).href)).default;

return getConfigList(getDefaultFromCjs(configFileExport), commandOptions);
async function getConfigFileExport(fileName: string, commandOptions: Record<string, unknown>) {
if (commandOptions.configPlugin || commandOptions.bundleConfigAsCjs) {
try {
return await loadTranspiledConfigFile(fileName, commandOptions);
} catch (err: any) {
if (err.message.includes('not defined in ES module scope')) {
return error(errCannotBundleConfigAsEsm(err));
}
throw err;
}
}
let cannotLoadEsm = false;
const handleWarning = (warning: Error): void => {
console.error(warning);
if (warning.message.includes('To load an ES module')) {
cannotLoadEsm = true;
}
};
process.on('warning', handleWarning);
try {
const fileUrl = pathToFileURL(fileName);
if (process.env.ROLLUP_WATCH) {
// We are adding the current date to allow reloads in watch mode
fileUrl.search = `?${Date.now()}`;
}
return (await import(fileUrl.href)).default;
} catch (err: any) {
if (cannotLoadEsm) {
return error(errCannotLoadConfigAsCjs(err));
}
if (err.message.includes('not defined in ES module scope')) {
return error(errCannotLoadConfigAsEsm(err));
}
throw err;
} finally {
process.off('warning', handleWarning);
}
}

function getDefaultFromCjs(namespace: GenericConfigObject): unknown {
Expand All @@ -55,7 +87,7 @@ function getDefaultFromCjs(namespace: GenericConfigObject): unknown {

async function loadTranspiledConfigFile(
fileName: string,
commandOptions: Record<string, unknown>
{ bundleConfigAsCjs, configPlugin, silent }: Record<string, unknown>
): Promise<unknown> {
const warnings = batchWarnings();
const inputOptions = {
Expand All @@ -66,17 +98,17 @@ async function loadTranspiledConfigFile(
plugins: [],
treeshake: false
};
await addPluginsFromCommandOption(commandOptions.configPlugin, inputOptions);
await addPluginsFromCommandOption(configPlugin, inputOptions);
const bundle = await rollup.rollup(inputOptions);
if (!commandOptions.silent && warnings.count > 0) {
if (!silent && warnings.count > 0) {
stderr(bold(`loaded ${relativeId(fileName)} with warnings`));
warnings.flush();
}
const {
output: [{ code }]
} = await bundle.generate({
exports: 'named',
format: 'es',
format: bundleConfigAsCjs ? 'cjs' : 'es',
plugins: [
{
name: 'transpile-import-meta',
Expand All @@ -91,11 +123,16 @@ async function loadTranspiledConfigFile(
}
]
});
return loadConfigFromBundledFile(fileName, code);
return loadConfigFromWrittenFile(
join(dirname(fileName), `rollup.config-${Date.now()}.${bundleConfigAsCjs ? 'cjs' : 'mjs'}`),
code
);
}

async function loadConfigFromBundledFile(fileName: string, bundledCode: string): Promise<unknown> {
const bundledFileName = join(dirname(fileName), `rollup.config-${Date.now()}.mjs`);
async function loadConfigFromWrittenFile(
bundledFileName: string,
bundledCode: string
): Promise<unknown> {
await fs.writeFile(bundledFileName, bundledCode);
try {
return (await import(pathToFileURL(bundledFileName).href)).default;
Expand Down
18 changes: 12 additions & 6 deletions docs/01-command-line-reference.md
Expand Up @@ -18,11 +18,9 @@ export default {
};
```

Typically, it is called `rollup.config.js` or `rollup.config.mjs` and sits in the root directory of your project. If you use the `.mjs` extension or have `type: "module"` in your `package.json` file, Rollup will directly use Node to import it, which is now the recommended way to define Rollup configurations. Note that there are some [caveats when using native Node ES modules](guide/en/#caveats-when-using-native-node-es-modules);
Typically, it is called `rollup.config.js` or `rollup.config.mjs` and sits in the root directory of your project. Unless the [`--configPlugin`](guide/en/#--configplugin-plugin) or [`--bundleConfigAsCjs`](guide/en/#--bundleconfigascjs) options are used, Rollup will directly use Node to import the file. Note that there are some [caveats when using native Node ES modules](guide/en/#caveats-when-using-native-node-es-modules) as Rollup will observe [Node ESM semantics](https://nodejs.org/docs/latest-v14.x/api/packages.html#packages_determining_module_system).

Otherwise, Rollup will transpile and bundle this file and its relative dependencies to CommonJS before requiring it to ensure compatibility with legacy code bases that use ES module syntax without properly respecting [Node ESM semantics](https://nodejs.org/docs/latest-v14.x/api/packages.html#packages_determining_module_system).

If you want to write your configuration as a CommonJS module using `require` and `module.exports`, you should change the file extension to `.cjs`, which will prevent Rollup from trying to transpile the CommonJS file.
If you want to write your configuration as a CommonJS module using `require` and `module.exports`, you should change the file extension to `.cjs`.

You can also use other languages for your configuration files like TypeScript. To do that, install a corresponding Rollup plugin like `@rollup/plugin-typescript` and use the [`--configPlugin`](guide/en/#--configplugin-plugin) option:

Expand Down Expand Up @@ -243,7 +241,7 @@ Besides `RollupOptions` and the `defineConfig` helper that encapsulates this typ
- `Plugin`: A plugin object that provides a `name` and some hooks. All hooks are fully typed to aid in plugin development.
- `PluginImpl`: A function that maps an options object to a plugin object. Most public Rollup plugins follow this pattern.
You can also directly write your config in TypeScript via the [`--configPlugin`](guide/en/#--configplugin-plugin) option. With TypeScript you can import the `RollupOptions` type directly:
You can also directly write your config in TypeScript via the [`--configPlugin`](guide/en/#--configplugin-plugin) option. With TypeScript, you can import the `RollupOptions` type directly:
```typescript
import type { RollupOptions } from 'rollup';
Expand Down Expand Up @@ -477,7 +475,15 @@ Note for Typescript: make sure you have the Rollup config file in your `tsconfig
"include": ["src/**/*", "rollup.config.ts"],
```

This option supports the same syntax as the [`--plugin`](guide/en/#-p-plugin---plugin-plugin) option i.e., you can specify the option multiple times, you can omit the `@rollup/plugin-` prefix and just write `typescript` and you can specify plugin options via `={...}`. Using this option will make Rollup transpile your configuration file to CommonJS first before executing it.
This option supports the same syntax as the [`--plugin`](guide/en/#-p-plugin---plugin-plugin) option i.e., you can specify the option multiple times, you can omit the `@rollup/plugin-` prefix and just write `typescript` and you can specify plugin options via `={...}`.

Using this option will make Rollup transpile your configuration file to an ES module first before executing it. To transpile to CommonJS instead, also pass the [`--bundleConfigAsCjs`](guide/en/#--bundleconfigascjs) option.

#### `--bundleConfigAsCjs`

This option will force your configuration to be transpiled to CommonJS.

This allows you to use CommonJS idioms like `__dirname` or `require.resolve` in your configuration even if the configuration itself is written as an ES module.

#### `-v`/`--version`

Expand Down
7 changes: 3 additions & 4 deletions package-lock.json

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

1 change: 0 additions & 1 deletion package.json
Expand Up @@ -85,7 +85,6 @@
"eslint-plugin-prettier": "^4.2.1",
"fixturify": "^2.1.1",
"fs-extra": "^10.1.0",
"get-package-type": "^0.1.0",
"github-api": "^3.4.0",
"hash.js": "^1.1.7",
"husky": "^8.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/rollup/types.d.ts
Expand Up @@ -28,6 +28,7 @@ export interface RollupLog {
pluginCode?: string;
pos?: number;
reexporter?: string;
stack?: string;
url?: string;
}

Expand Down
31 changes: 31 additions & 0 deletions src/utils/error.ts
Expand Up @@ -71,6 +71,7 @@ const ADDON_ERROR = 'ADDON_ERROR',
ILLEGAL_REASSIGNMENT = 'ILLEGAL_REASSIGNMENT',
INPUT_HOOK_IN_OUTPUT_PLUGIN = 'INPUT_HOOK_IN_OUTPUT_PLUGIN',
INVALID_CHUNK = 'INVALID_CHUNK',
INVALID_CONFIG_MODULE_FORMAT = 'MISSING_CONFIGINVALID_CONFIG_MODULE_FORMAT',
INVALID_EXPORT_OPTION = 'INVALID_EXPORT_OPTION',
INVALID_EXTERNAL_ID = 'INVALID_EXTERNAL_ID',
INVALID_OPTION = 'INVALID_OPTION',
Expand Down Expand Up @@ -364,6 +365,36 @@ export function errCannotAssignModuleToChunk(
};
}

export function errCannotBundleConfigAsEsm(originalError: Error): RollupLog {
return {
cause: originalError,
code: INVALID_CONFIG_MODULE_FORMAT,
message: `Rollup transpiled your configuration to an ES module even though it appears to contain CommonJS elements. To resolve this, you can pass the "--bundleConfigAsCjs" flag to Rollup or change your configuration to only contain valid ESM code.\n\nOriginal error: ${originalError.message}`,
stack: originalError.stack,
url: 'https://rollupjs.org/guide/en/#--bundleconfigascjs'
};
}

export function errCannotLoadConfigAsCjs(originalError: Error): RollupLog {
return {
cause: originalError,
code: INVALID_CONFIG_MODULE_FORMAT,
message: `Node tried to load your configuration file as CommonJS even though it is likely an ES module. To resolve this, change the extension of your configuration to ".mjs", set "type": "module" in your package.json file or pass the "--bundleConfigAsCjs" flag.\n\nOriginal error: ${originalError.message}`,
stack: originalError.stack,
url: 'https://rollupjs.org/guide/en/#--bundleconfigascjs'
};
}

export function errCannotLoadConfigAsEsm(originalError: Error): RollupLog {
return {
cause: originalError,
code: INVALID_CONFIG_MODULE_FORMAT,
message: `Node tried to load your configuration as an ES module even though it is likely CommonJS. To resolve this, change the extension of your configuration to ".cjs" or pass the "--bundleConfigAsCjs" flag.\n\nOriginal error: ${originalError.message}`,
stack: originalError.stack,
url: 'https://rollupjs.org/guide/en/#--bundleconfigascjs'
};
}

export function errInvalidExportOptionValue(optionValue: string): RollupLog {
return {
code: INVALID_EXPORT_OPTION,
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/mergeOptions.ts
Expand Up @@ -57,6 +57,7 @@ export function mergeOptions(
Object.keys(inputOptions).concat(
Object.keys(outputOptions[0]).filter(option => option !== 'sourcemapPathTransform'),
Object.keys(commandAliases),
'bundleConfigAsCjs',
'config',
'environment',
'plugin',
Expand Down
1 change: 0 additions & 1 deletion test/cli/samples/config-defineConfig-mjs/_config.js
@@ -1,6 +1,5 @@
module.exports = {
description: 'uses mjs config file which return config wrapped by defineConfig',
command: 'rollup --config rollup.config.mjs',
minNodeVersion: 13,
execute: true
};
2 changes: 1 addition & 1 deletion test/cli/samples/config-es6/_config.js
@@ -1,5 +1,5 @@
module.exports = {
description: 'uses ES6 module config file',
command: 'rollup --config rollup.config.js',
command: 'rollup --config rollup.config.js --bundleConfigAsCjs',
execute: true
};
2 changes: 1 addition & 1 deletion test/cli/samples/config-function-modify-command/_config.js
@@ -1,5 +1,5 @@
module.exports = {
description: 'allows cleaning up and modifying the command args in the config file',
command: 'rollup --config rollup.config.js --some-option="foo" --another-option=42',
command: 'rollup --config rollup.config.mjs --some-option="foo" --another-option=42',
execute: true
};
4 changes: 2 additions & 2 deletions test/cli/samples/config-function-modify-command/main.js
@@ -1,7 +1,7 @@
assert.deepEqual(COMMAND_OPTIONS, {
_: [],
config: 'rollup.config.js',
c: 'rollup.config.js',
config: 'rollup.config.mjs',
c: 'rollup.config.mjs',
'some-option': 'foo',
'another-option': 42
});
2 changes: 1 addition & 1 deletion test/cli/samples/config-function/_config.js
@@ -1,6 +1,6 @@
module.exports = {
description:
'if the config file returns a function then this will be called with the command args',
command: 'rollup --config rollup.config.js --silent',
command: 'rollup --config rollup.config.mjs --silent',
execute: true
};
4 changes: 2 additions & 2 deletions test/cli/samples/config-function/main.js
@@ -1,6 +1,6 @@
assert.deepEqual(COMMAND_OPTIONS, {
_: [],
config: 'rollup.config.js',
c: 'rollup.config.js',
config: 'rollup.config.mjs',
c: 'rollup.config.mjs',
silent: true
});
2 changes: 1 addition & 1 deletion test/cli/samples/config-import-meta/_config.js
@@ -1,4 +1,4 @@
module.exports = {
description: 'uses correct import.meta.url in config files',
command: 'rollup -c'
command: 'rollup -c --bundleConfigAsCjs'
};
2 changes: 1 addition & 1 deletion test/cli/samples/config-json/_config.js
@@ -1,5 +1,5 @@
module.exports = {
description: 'allows config file to import json',
command: 'rollup --config rollup.config.js',
command: 'rollup --config rollup.config.js --bundleConfigAsCjs',
execute: true
};
1 change: 0 additions & 1 deletion test/cli/samples/config-mjs-plugins/_config.js
@@ -1,5 +1,4 @@
module.exports = {
description: 'supports native esm as well as CJS plugins when using .mjs in Node 13+',
minNodeVersion: 13,
command: 'rollup -c'
};
2 changes: 1 addition & 1 deletion test/cli/samples/config-multiple-source-maps/_config.js
@@ -1,4 +1,4 @@
module.exports = {
description: 'correctly generates sourcemaps for multiple outputs',
command: 'rollup -c'
command: 'rollup -c --bundleConfigAsCjs'
};

0 comments on commit 96e8200

Please sign in to comment.