Skip to content

Commit 88685ea

Browse files
committed
Update tests
1 parent 5714880 commit 88685ea

23 files changed

+959
-675
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ A Rollup plugin that automatically declares NodeJS built-in modules as `external
33

44
Works in monorepos too!
55

6-
> ## Breaking changes in version 5
7-
> - In previous versions, the `devDeps` option (see below) defaulted to `true`.<br>This was practical, but often wrong: devDependencies are ment just for that: being used when developping. Therefore, the `devDeps` option now defaults to `false`, meaning Rollup will include them in your bundle.
6+
> ### Breaking changes in version 5
7+
> - In previous versions, the `devDeps` option (see below) defaulted to `true`.<br>This was practical, but often wrong: devDependencies are meant just for that: being used when developping. Therefore, the `devDeps` option now defaults to `false`, meaning Rollup will include them in your bundle.
88
>- As anticipated since v4, the `builtinsPrefix` option now defaults to `'add'`.
99
>- The deprecated `prefixedBuiltins` option has been removed.
10-
> - `rollup-plugin-node-externals` no longer depends on the Find Up package. While this is not a breaking change per se, it can be in some edge situations.
10+
> - `rollup-plugin-node-externals` no longer depends on the Find-Up package (while this is not a breaking change per se, it can be in some edge situations).
11+
> - Now has a _peer dependency_ on `rollup ^2.60.0 || ^3.0.0`.
12+
13+
> ### Breaking changes in version 4
14+
> - In previous versions, the `deps` option (see below) defaulted to `false`.<br>This was practical, but often wrong: when bundling for distribution, you want your own dependencies to be installed by the package manager alongside your package, so they should not be bundled in the code. Therefore, the `deps` option now defaults to `true`.
15+
> - Now requires Node 14 (up from Node 12 for previous versions).
16+
> - Now has a _peer dependency_ on `rollup ^2.60.0`.
1117
1218
## Why you need this
1319
<details><summary>(click to expand)</summary>
@@ -101,9 +107,9 @@ Set the `builtins` option to `false` if you'd like to use some shims/polyfills f
101107

102108
#### builtinsPrefix?: 'add' | 'strip' = 'add'
103109
How to handle the `node:` scheme used in recent versions of Node (i.e., `import path from 'node:path'`).<br>
104-
_Note that prefix handling is independant of the `builtins` options being enabled or disabled._
105110
- If `add` (the default), the `node:` prefix is always added. In effect, this homogenizes all your imports of node builtins to their prefixed version.
106111
- If `strip` (the default), the import is always resolved unprefixed. In effect, this homogenizes all your imports of node builtins to their unprefixed version.
112+
> _Note that prefix handling is independant of the `builtins` options being enabled or disabled._
107113
108114
#### packagePath?: string | string[] = []
109115
If you're working with monorepos, the `packagePath` option is made for you. It can take a path, or an array of paths, to your package.json file(s). If not specified, the default is to start with the current directory's package.json then go up scan for all package.json files in parent directories recursively until either the root git directory is reached or until no other package.json can be found.

package-lock.json

Lines changed: 598 additions & 492 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rollup.config.mjs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
// @ts-check
2-
import { builtinModules } from 'node:module'
3-
import { fileURLToPath } from 'node:url'
2+
import { createRequire, builtinModules } from 'node:module'
43
import path from 'node:path'
5-
import fs from 'node:fs/promises'
64
import nodeResolve from '@rollup/plugin-node-resolve'
75
import commonjs from '@rollup/plugin-commonjs'
86
import typescript from 'rollup-plugin-ts'
97
import { defineConfig } from 'rollup'
108

11-
const __filename = fileURLToPath(import.meta.url)
12-
const __dirname = path.dirname(__filename)
13-
149
/** @type { import('./package.json') } */
15-
const pkg = JSON.parse(await fs.readFile(path.resolve(__dirname, './package.json'), 'utf-8'))
10+
const pkg = createRequire(import.meta.url)('./package.json')
1611

1712
const builtins = new Set(builtinModules)
1813

@@ -39,8 +34,7 @@ const sharedOutputOptions = {
3934
preset: 'es2015',
4035
symbols: false,
4136
},
42-
esModule: false,
43-
exports: 'named',
37+
freeze: false,
4438
sourcemap: true,
4539
plugins: [
4640
packageType()
@@ -54,10 +48,16 @@ export default defineConfig({
5448
format: 'commonjs',
5549
file: pkg.exports.require,
5650
interop: id => id && (id.startsWith('node:') || builtins.has(id)) ? 'default' : 'auto',
57-
...sharedOutputOptions
51+
esModule: false,
52+
exports: 'named',
53+
...sharedOutputOptions,
54+
55+
// Using the same technique as rollup/plugins
56+
// (see https://github.com/rollup/plugins/blob/a87282241be1ab5059ed8cffae24d01660fae07d/shared/rollup.config.mjs#L28)
57+
footer: 'module.exports = Object.assign(exports.default, exports);',
5858
},
5959
{
60-
format: 'module',
60+
format: 'esm',
6161
file: pkg.exports.import,
6262
...sharedOutputOptions
6363
}
@@ -67,7 +67,7 @@ export default defineConfig({
6767
commonjs(),
6868
typescript({
6969
hook: {
70-
outputPath: (_path, kind) => kind === 'declaration' ? path.normalize(pkg.exports.types) : undefined
70+
outputPath: (_, kind) => kind === 'declaration' ? path.normalize(pkg.exports.types) : undefined
7171
}
7272
})
7373
]

src/index.ts

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,38 @@ export interface ExternalsOptions {
7474

7575
// Listing only fields of interest in package.json
7676
interface PackageJson {
77-
dependencies?: Record<string, string>
78-
devDependencies?: Record<string, string>
79-
peerDependencies?: Record<string, string>
77+
dependencies?: Record<string, string>
78+
devDependencies?: Record<string, string>
79+
peerDependencies?: Record<string, string>
8080
optionalDependencies?: Record<string, string>
81+
workspaces?: string[]
82+
packages?: string[]
8183
}
8284

8385
// Prepare node built-in modules lists.
86+
const nodePrefix = 'node:'
8487
const nodePrefixRx = /^node:/
8588
const builtins = {
86-
all: new Set([
87-
...builtinModules,
88-
...builtinModules.map(mod => 'node:' + mod.replace(nodePrefixRx, ''))
89-
]),
89+
all: new Set(builtinModules),
9090
alwaysPrefixed: new Set(
9191
builtinModules.filter(mod => nodePrefixRx.test(mod))
9292
)
9393
}
9494

95+
const workspaceRootFiles = new Set([
96+
'pnpm-workspace.yaml', // pnpm
97+
'lerna.json', // Lerna
98+
'workspace.jsonc', // Bit
99+
'nx.json', // Nx
100+
'rush.json', // Rush
101+
])
102+
95103
// Our defaults
96-
const defaults: Required<ExternalsOptions> = {
104+
type Config = Required<ExternalsOptions> & {
105+
invalid?: boolean
106+
}
107+
108+
const defaults: Config = {
97109
builtins: true,
98110
builtinsPrefix: 'add',
99111
packagePath: [],
@@ -114,11 +126,11 @@ const isString = (str: unknown): str is string =>
114126
*/
115127
function externals(options: ExternalsOptions = {}): Plugin {
116128

117-
// This will store all eventual warnings until we can display them.
118-
const warnings: string[] = []
129+
// This will store all eventual warnings/errors until we can display them.
130+
const messages: string[] = []
119131

120132
// Consolidate options
121-
const config: typeof defaults = Object.assign(Object.create(defaults), options)
133+
const config: Config = Object.assign(Object.create(null), defaults, options)
122134

123135
// Map the include and exclude options to arrays of regexes.
124136
const [ include, exclude ] = [ 'include', 'exclude' ].map(option =>
@@ -130,7 +142,7 @@ function externals(options: ExternalsOptions = {}): Plugin {
130142
else if (typeof entry === 'string')
131143
result.push(new RegExp('^' + entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '$'))
132144
else if (entry) {
133-
warnings.push(`Ignoring wrong entry type #${index} in '${option}' option: ${JSON.stringify(entry)}`)
145+
messages.push(`Ignoring wrong entry type #${index} in '${option}' option: ${JSON.stringify(entry)}`)
134146
}
135147
return result
136148
}, [] as RegExp[])
@@ -145,26 +157,29 @@ function externals(options: ExternalsOptions = {}): Plugin {
145157
? [ config.packagePath ]
146158
: []
147159

148-
// Get all package.json files from cwd up to the root of the git repo
149-
// or the root of the volume, whichever comes first.
150160
if (packagePaths.length === 0) {
151-
let cwd = process.cwd()
152-
for (;;) {
153-
let name = path.join(cwd, 'package.json')
154-
if (fs.statSync(name, { throwIfNoEntry: false })?.isFile())
155-
packagePaths.push(name)
156-
157-
name = path.join(cwd, '.git')
158-
if (fs.statSync(name, { throwIfNoEntry: false })?.isDirectory())
159-
break
160-
161-
const parent = path.dirname(cwd)
162-
if (parent === cwd)
161+
// Get all package.json files from cwd up to the root of the git repo,
162+
// the root of the monorepo, or the root of the volume, whichever comes first.
163+
for (
164+
let current = process.cwd(), previous: string | null = null;
165+
previous !== current;
166+
previous = current, current = path.dirname(current)
167+
) {
168+
const entries = fs.readdirSync(current, { withFileTypes: true })
169+
170+
if (entries.some(entry => entry.name === 'package.json' && entry.isFile()))
171+
packagePaths.push(path.join(current, 'package.json'))
172+
173+
if (entries.some(entry =>
174+
(workspaceRootFiles.has(entry.name) && entry.isFile()) ||
175+
(entry.name === '.git' && entry.isDirectory())
176+
)) {
163177
break
164-
cwd = parent
178+
}
165179
}
166180
}
167181

182+
// Gather dependencies names.
168183
const dependencies: Record<string, string> = {}
169184
for (const packagePath of packagePaths) {
170185
let pkg: PackageJson | null = null
@@ -176,12 +191,17 @@ function externals(options: ExternalsOptions = {}): Plugin {
176191
config.peerDeps && pkg.peerDependencies,
177192
config.optDeps && pkg.optionalDependencies
178193
)
194+
195+
// Stop here if this is a npm/yarn workspace root
196+
if (pkg.workspaces || pkg.packages)
197+
break
179198
}
180199
catch {
200+
config.invalid = true
181201
if (pkg)
182-
warnings.push(`File ${JSON.stringify(packagePath)} does not look like a valid package.json.`)
202+
messages.push(`File ${JSON.stringify(packagePath)} does not look like a valid package.json.`)
183203
else if (config.packagePath.length) // string or array
184-
warnings.push(`Cannot read file ${JSON.stringify(packagePath)}`)
204+
messages.push(`Cannot read file ${JSON.stringify(packagePath)}`)
185205
}
186206
}
187207

@@ -197,10 +217,15 @@ function externals(options: ExternalsOptions = {}): Plugin {
197217
name: 'node-externals',
198218

199219
async buildStart() {
200-
201-
// Simply issue the warnings we may have collected earlier.
202-
while (warnings.length > 0)
203-
this.warn(warnings.shift()!)
220+
// Bail out if there was an error.
221+
if (config.invalid)
222+
this.error(messages[0])
223+
224+
// Otherwise issue any warnings we may have collected earlier.
225+
while (messages.length > 0) {
226+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227+
this.warn(messages.shift()!)
228+
}
204229
},
205230

206231
async resolveId(id) {
@@ -210,13 +235,14 @@ function externals(options: ExternalsOptions = {}): Plugin {
210235
return null
211236

212237
// Handle node builtins.
213-
if (builtins.all.has(id)) {
238+
if (id.startsWith(nodePrefix) || builtins.all.has(id)) {
214239
const stripped = id.replace(nodePrefixRx, '')
215240
return {
216241
id: config.builtinsPrefix === 'add' || builtins.alwaysPrefixed.has(id)
217-
? 'node:' + stripped
242+
? nodePrefix + stripped
218243
: stripped,
219-
external: config.builtins && !isExcluded(id)
244+
external: config.builtins && !isExcluded(id),
245+
moduleSideEffects: false
220246
}
221247
}
222248

test/_common.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Plugin } from 'rollup'
2+
3+
// node-externals only implemented these two hooks
4+
export type TestedPlugin = {
5+
buildStart: Plugin['buildStart']
6+
resolveId: Plugin['resolveId']
7+
}
8+
9+
const fakePluginContext = {
10+
error(msg: string) {
11+
throw new Error(msg)
12+
},
13+
14+
warn(msg: string) {
15+
console.warn(msg)
16+
}
17+
}
18+
19+
export async function call(hook: TestedPlugin[keyof TestedPlugin], ...args: any[]) {
20+
if (typeof hook === 'function')
21+
return (hook as any).apply(fakePluginContext, args)
22+
if (typeof hook === 'object' && 'handler' in hook)
23+
return (hook.handler as any).apply(fakePluginContext, args)
24+
25+
throw new Error('Ooops')
26+
}

test/builtins.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import test from 'ava'
2+
import { call, type TestedPlugin } from './_common'
3+
import externals from '../src/index'
4+
5+
test('Adds "node:" prefix to builtins by default', async t => {
6+
const plugin = externals() as TestedPlugin
7+
8+
for (const builtin of [ 'node:path', 'path' ]) {
9+
t.like(await call(plugin.resolveId, builtin), {
10+
id: 'node:path',
11+
external: true
12+
})
13+
}
14+
})
15+
16+
test('Removes "node:" prefix when option "builtinsPrefix" is set to "strip"', async t => {
17+
const plugin = externals({
18+
builtinsPrefix: 'strip'
19+
}) as TestedPlugin
20+
21+
for (const builtin of [ 'node:path', 'path' ]) {
22+
t.like(await call(plugin.resolveId, builtin), {
23+
id: 'path',
24+
external: true
25+
})
26+
}
27+
})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"packages": [
3+
"one", "two "
4+
]
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"chalk": "^4.0.0"
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"express": "4.17.1"
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"lodash": "^4.17.15"
4+
}
5+
}

0 commit comments

Comments
 (0)