diff --git a/LICENSE.md b/LICENSE.md index 0ac3a7e208e..98e89d8a276 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -240,6 +240,35 @@ 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 diff --git a/cli/run/loadConfigFile.ts b/cli/run/loadConfigFile.ts index 1d407162e7a..8d59fce3f30 100644 --- a/cli/run/loadConfigFile.ts +++ b/cli/run/loadConfigFile.ts @@ -1,6 +1,6 @@ import { extname, isAbsolute } from 'path'; -import { version } from 'process'; import { pathToFileURL } from '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'; @@ -12,10 +12,6 @@ import { stderr } from '../logging'; import batchWarnings, { type BatchWarnings } from './batchWarnings'; import { addCommandPluginsToInputOptions, addPluginsFromCommandOption } from './commandPlugins'; -function supportsNativeESM(): boolean { - return Number(/^v(\d+)/.exec(version)![1]) >= 13; -} - interface NodeModuleWithCompile extends NodeModule { _compile(code: string, filename: string): any; } @@ -48,11 +44,10 @@ async function loadConfigFile( const configFileExport = commandOptions.configPlugin || - !(extension === '.cjs' || (extension === '.mjs' && supportsNativeESM())) + // We always transpile the .js non-module case because many legacy code bases rely on this + (extension === '.js' && getPackageType.sync(fileName) !== 'module') ? await getDefaultFromTranspiledConfigFile(fileName, commandOptions) - : extension === '.cjs' - ? getDefaultFromCjs(require(fileName)) - : (await import(pathToFileURL(fileName).href)).default; + : getDefaultFromCjs((await import(pathToFileURL(fileName).href)).default); return getConfigList(configFileExport, commandOptions); } diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index ae7a7756782..89cebe125e9 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -18,9 +18,11 @@ export default { }; ``` -Typically, it is called `rollup.config.js` and sits in the root directory of your project. Behind the scenes, Rollup will usually transpile and bundle this file and its relative dependencies to CommonJS before requiring it. This has the advantage that you can share code with an ES module code base while having full interoperability with the Node ecosystem. +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); -If you want to write your config 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 file. Furthermore if you are on Node 13+, changing the file extension to `.mjs` will also prevent Rollup from transpiling it but import the file as an ES module instead. See [using untranspiled config files](guide/en/#using-untranspiled-config-files) for more details and why you might want to do this. +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. 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: @@ -28,7 +30,7 @@ You can also use other languages for your configuration files like TypeScript. T rollup --config rollup.config.ts --configPlugin typescript ``` -Also have a look at [Config Intellisense](guide/en/#config-intellisense) for more ways to use TypeScript typings in your config files. +Using the `--configPlugin` option will always force your config file to be transpiled to CommonJS first. Also have a look at [Config Intellisense](guide/en/#config-intellisense) for more ways to use TypeScript typings in your config files. Config files support the options listed below. Consult the [big list of options](guide/en/#big-list-of-options) for details on each option: @@ -266,66 +268,72 @@ For interoperability, Rollup also supports loading configuration files from pack rollup --config node:my-special-config ``` -### Using untranspiled config files +### Caveats when using native Node ES modules -By default, Rollup will expect config files to be ES modules and bundle and transpile them and their relative imports to CommonJS before requiring them. This is a fast process and has the advantage that it is easy to share code between your configuration and an ES module code base. If you want to write your configuration as CommonJS instead, you can skip this process by using the `.cjs` extension: +Especially when upgrading from an older Rollup version, there are some things you need to be aware of when using a native ES module for your configuration file. -```javascript -// rollup.config.cjs -module.exports = { - input: 'src/main.js', - output: { - file: 'bundle.js', - format: 'cjs' - } -}; -``` +#### Getting the current directory -It may be pertinent if you want to use the config file not only from the command line, but also from your custom scripts programmatically. +With CommonJS files, people often use `__dirname` to access the current directory and resolve relative paths to absolute paths. This is not supported for native ES modules. Instead, we recommend the following approach e.g. to generate an absolute id for an external module: -On the other hand if you are using at least Node 13 and have `"type": "module"` in your `package.json` file, Rollup's transpilation will prevent your configuration file from importing packages that are themselves ES modules. In that case, changing your file extension to `.mjs` will instruct Rollup to import your configuration directly as an ES module. However, note that this is specific to Node 13+; on older Node versions, `.mjs` is treated just like `.js`. +```js +// rollup.config.js +import { fileURLToPath } from 'url' -There are some potential gotchas when using `.mjs` on Node 13+: +export default { + ..., + // generates an absolute path for /src/some-external-file.js + external: [fileURLToPath(new URL('src/some-external-file.js', import.meta.url))] +}; +``` -- You will only get a default export from CommonJS plugins -- You may not be able to import JSON files such as your `package.json file`. There are four ways to go around this: +#### Importing package.json - - read and parse the JSON file yourself via +It can be useful to import your package file to e.g. mark your dependencies as "external" automatically. Depending on your Node version, there are different ways of doing that: - ``` - // rollup.config.mjs - import { readFileSync } from 'fs'; +- For Node 17.5+, you can use an import assertion - const packageJson = JSON.parse(readFileSync('./package.json')); - ... - ``` + ```js + import pkg from './package.json' assert { type: 'json' }; - - use `createRequire` via + export default { + input: 'src/main.js', + external: Object.keys(pkg.dependencies), + output: { + format: 'es', + dir: 'dist' + } + }; + ``` - ``` - // rollup.config.mjs - import { createRequire } from 'module'; - const require = createRequire(import.meta.url); - const packageJson = require('./package.json'); - ... - ``` +- For older Node version, you can use "createRequire" - - run Rollup CLI via + ```js + import { createRequire } from 'module'; + const require = createRequire(import.meta.url); + const pkg = require('./package.json'); - ``` - node --experimental-json-modules ./node_modules/.bin/rollup --config - ``` + export default { + input: 'src/main.js', + external: Object.keys(pkg.dependencies), + output: { + format: 'es', + dir: 'dist' + } + }; + ``` - - create a CommonJS wrapper that requires the JSON file: +- Or just directly read and parse the file from disk - ```js - // load-package.cjs - module.exports = require('./package.json'); + ```js + // rollup.config.mjs + import { readFileSync } from 'fs'; + import { fileURLToPath } from 'url'; - // rollup.config.mjs - import pkg from './load-package.cjs'; - ... - ``` + const pkgFileName = fileURLToPath(new URL('./package.json', import.meta.url)); + const pkg = JSON.parse(readFileSync(pkgFileName)); + // ... + ``` ### Command line flags @@ -473,7 +481,7 @@ 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 `={...}`. +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. #### `-v`/`--version` diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index 9684ac61c13..d54b035ec4e 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -15,13 +15,13 @@ Either a function that takes an `id` and returns `true` (external) or `false` (n ```js // rollup.config.js -import path from 'path'; +import { fileURLToPath } from 'url' export default { ..., external: [ 'some-externally-required-library', - path.resolve( __dirname, 'src/some-local-file-that-should-not-be-bundled.js' ), + fileURLToPath(new URL('src/some-local-file-that-should-not-be-bundled.js', import.meta.url)), /node_modules/ ] }; @@ -183,8 +183,8 @@ To tell Rollup that a local file should be replaced by a global variable, use an ```js // rollup.config.js -import path from 'path'; -const externalId = path.resolve( __dirname, 'src/some-local-file-that-should-not-be-bundled.js' ); +import { fileURLToPath } from 'url' +const externalId = fileURLToPath(new URL('src/some-local-file-that-should-not-be-bundled.js', import.meta.url)) export default { ..., diff --git a/package-lock.json b/package-lock.json index 812c4ef1c59..69b580d5531 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "execa": "^6.1.0", "fixturify": "^2.1.1", "fs-extra": "^10.1.0", + "get-package-type": "^0.1.0", "hash.js": "^1.1.7", "husky": "^8.0.1", "is-reference": "^3.0.0", diff --git a/package.json b/package.json index 82ea56523dc..ed58e475552 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "execa": "^6.1.0", "fixturify": "^2.1.1", "fs-extra": "^10.1.0", + "get-package-type": "^0.1.0", "hash.js": "^1.1.7", "husky": "^8.0.1", "is-reference": "^3.0.0", diff --git a/scripts/perf-init.js b/scripts/perf-init.js index 86bdc0a6b5e..9f719110c00 100644 --- a/scripts/perf-init.js +++ b/scripts/perf-init.js @@ -1,12 +1,11 @@ /* eslint-disable no-console */ -import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import { execa } from 'execa'; import fs from 'fs-extra'; import { findConfigFileName } from './find-config.js'; -const TARGET_DIR = path.resolve(dirname(fileURLToPath(import.meta.url)), '..', 'perf'); +const TARGET_DIR = fileURLToPath(new URL('../perf', import.meta.url).href); const VALID_REPO = /^([^/\s#]+\/[^/\s#]+)(#([^/\s#]+))?$/; const repoWithBranch = process.argv[2]; diff --git a/scripts/perf.js b/scripts/perf.js index 03f730c38e6..989588efbe0 100644 --- a/scripts/perf.js +++ b/scripts/perf.js @@ -2,7 +2,6 @@ /* global gc */ import { readFileSync, writeFileSync } from 'fs'; -import path, { dirname } from 'path'; import { cwd } from 'process'; import { fileURLToPath } from 'url'; import { createColors } from 'colorette'; @@ -12,8 +11,8 @@ import { rollup } from '../dist/rollup.js'; import { findConfigFileName } from './find-config.js'; const initialDir = cwd(); -const targetDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '..', 'perf'); -const perfFile = path.resolve(targetDir, 'rollup.perf.json'); +const targetDir = fileURLToPath(new URL('../perf', import.meta.url).href); +const perfFile = fileURLToPath(new URL('../perf/rollup.perf.json', import.meta.url).href); const { bold, underline, cyan, red, green } = createColors(); const MIN_ABSOLUTE_TIME_DEVIATION = 10; const RELATIVE_DEVIATION_FOR_COLORING = 5; diff --git a/test/cli/samples/config-no-module/_config.js b/test/cli/samples/config-no-module/_config.js index e481b95c1b4..d2ab16674de 100644 --- a/test/cli/samples/config-no-module/_config.js +++ b/test/cli/samples/config-no-module/_config.js @@ -3,7 +3,7 @@ const { assertIncludes } = require('../../../utils.js'); module.exports = { description: 'provides a helpful error message if a transpiled config is interpreted as "module"', minNodeVersion: 13, - command: 'cd sub && rollup -c', + command: 'rollup -c', error: () => true, stderr: stderr => assertIncludes( diff --git a/test/cli/samples/config-no-module/sub/rollup.config.js b/test/cli/samples/config-no-module/rollup.config.js similarity index 82% rename from test/cli/samples/config-no-module/sub/rollup.config.js rename to test/cli/samples/config-no-module/rollup.config.js index 28f443866ee..c9356224f7f 100644 --- a/test/cli/samples/config-no-module/sub/rollup.config.js +++ b/test/cli/samples/config-no-module/rollup.config.js @@ -1,7 +1,7 @@ import { shebang } from 'rollup-plugin-thatworks'; export default { - input: 'main.js', + input: './sub/main.js', output: { format: 'cjs' }, plugins: [shebang()] }; diff --git a/test/cli/samples/config-type-module/_config.js b/test/cli/samples/config-type-module/_config.js new file mode 100644 index 00000000000..5d1b2a54a2f --- /dev/null +++ b/test/cli/samples/config-type-module/_config.js @@ -0,0 +1,18 @@ +const { assertIncludes } = require('../../../utils.js'); + +module.exports = { + description: 'tries to load .js config file if package type is "module"', + command: 'cd sub && rollup -c rollup.config.js', + error: () => true, + stderr: stderr => { + assertIncludes( + stderr, + '[!] ReferenceError: module is not defined in ES module scope\n' + + "This file is being treated as an ES module because it has a '.js' file extension and" + ); + assertIncludes( + stderr, + 'contains "type": "module". To treat it as a CommonJS script, rename it to use the \'.cjs\' file extension.' + ); + } +}; diff --git a/test/cli/samples/config-type-module/sub/main.js b/test/cli/samples/config-type-module/sub/main.js new file mode 100644 index 00000000000..753a47d529e --- /dev/null +++ b/test/cli/samples/config-type-module/sub/main.js @@ -0,0 +1 @@ +console.log(42); diff --git a/test/cli/samples/config-type-module/sub/package.json b/test/cli/samples/config-type-module/sub/package.json new file mode 100644 index 00000000000..bedb411a912 --- /dev/null +++ b/test/cli/samples/config-type-module/sub/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/cli/samples/config-type-module/sub/rollup.config.js b/test/cli/samples/config-type-module/sub/rollup.config.js new file mode 100644 index 00000000000..d3fce68c3a7 --- /dev/null +++ b/test/cli/samples/config-type-module/sub/rollup.config.js @@ -0,0 +1,6 @@ +module.exports = { + input: 'main.js', + output: { + format: 'cjs' + } +}; diff --git a/typings/declarations.d.ts b/typings/declarations.d.ts index 51f94dc1e2c..846464c182e 100644 --- a/typings/declarations.d.ts +++ b/typings/declarations.d.ts @@ -99,3 +99,11 @@ declare module 'is-reference' { value: Node; }; } + +declare module 'get-package-type' { + interface GetPackageType { + sync(fileName: string): 'module' | 'commonjs'; + } + const getPackageType: GetPackageType; + export default getPackageType; +}