Skip to content

Commit 7aa030d

Browse files
committed
Fix minor flaw in inclusion logic
1 parent a51b58c commit 7aa030d

File tree

2 files changed

+87
-60
lines changed

2 files changed

+87
-60
lines changed

src/index.ts

Lines changed: 86 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ function externals(options: ExternalsOptions = {}): Plugin {
4242
const warnings: string[] = []
4343

4444
// Consolidate options
45-
const config: Required<ExternalsOptions> = {
45+
const config: Required<ExternalsOptions> & { _builtinsPrefix: 'ignore' | 'add' | 'strip' } = {
4646
builtins: true,
47-
builtinsPrefix: 'add',
47+
builtinsPrefix: 'strip', // Will be be 'add' v5
48+
prefixedBuiltins: 'strip', // Will be removed in v5
4849
packagePath: [],
4950
deps: true,
5051
devDeps: true,
@@ -53,23 +54,31 @@ function externals(options: ExternalsOptions = {}): Plugin {
5354
include: [],
5455
exclude: [],
5556

56-
prefixedBuiltins: 'strip',
57+
...options,
5758

58-
...options
59+
_builtinsPrefix: 'strip', // Used to handle prefixes until v5
5960
}
6061

61-
if ('prefixedBuiltins' in options) {
62+
if ('builtinsPrefix' in options) {
63+
config._builtinsPrefix = options.builtinsPrefix
64+
}
65+
else if ('prefixedBuiltins' in options) {
6266
warnings.push("The 'prefixedBuiltins' option is now deprecated, " +
6367
"please use 'builtinsPrefix' instead to silent this warning.")
64-
}
65-
else if ('builtinsPrefix' in options) {
66-
config.prefixedBuiltins = options.builtinsPrefix
68+
69+
const { prefixedBuiltins } = options
70+
config._builtinsPrefix =
71+
prefixedBuiltins === false
72+
? 'ignore'
73+
: prefixedBuiltins === true || prefixedBuiltins === 'add'
74+
? 'add'
75+
: 'strip'
6776
}
6877

6978
// Map the include and exclude options to arrays of regexes.
7079
const [ include, exclude ] = [ 'include', 'exclude' ].map(option =>
7180
([] as (string | RegExp)[])
72-
.concat(config[option as 'include' | 'exclude'])
81+
.concat(config[ option as 'include' | 'exclude' ])
7382
.reduce((result, entry, index) => {
7483
if (entry instanceof RegExp)
7584
result.push(entry)
@@ -82,85 +91,102 @@ function externals(options: ExternalsOptions = {}): Plugin {
8291
}, [] as RegExp[])
8392
)
8493

94+
// Support for builtin modules.
95+
const nodePrefix = 'node:'
96+
const nodePrefixRx = /^node:/
97+
const _builtinModules = {
98+
bare: [] as string[], // w/o schemed-only builtins
99+
schemed: [] as string[], // w/ schemed-only builtins
100+
alwaysSchemed: [] as string[] // e.g., node:test in node 18+
101+
}
102+
103+
builtinModules.forEach(builtin => {
104+
if (builtin.startsWith(nodePrefix)) {
105+
_builtinModules.schemed.push(builtin)
106+
_builtinModules.alwaysSchemed.push(builtin)
107+
}
108+
else {
109+
_builtinModules.bare.push(builtin)
110+
_builtinModules.schemed.push(nodePrefix + builtin)
111+
}
112+
})
113+
114+
const builtins = new Set([
115+
..._builtinModules.bare,
116+
..._builtinModules.schemed
117+
])
118+
const alwaysSchemed = new Set(_builtinModules.alwaysSchemed)
119+
85120
// A filter function to keep only non excluded dependencies.
86121
const isNotExcluded = (id: string) => !exclude.some(rx => rx.test(id))
87122

88123
// The array of the final regexes.
89124
let externals: RegExp[] = []
90125
const isExternal = (id: string) => externals.some(rx => rx.test(id))
91126

92-
// Support for builtin modules.
93-
const builtins: Set<string> = new Set(),
94-
alwaysSchemed: Set<string> = new Set()
95-
if (config.builtins) {
96-
const filtered = builtinModules.filter(b => isNotExcluded(b) && isNotExcluded('node:' + b))
97-
for (const builtin of filtered) {
98-
builtins.add(builtin)
99-
if (builtin.startsWith('node:'))
100-
alwaysSchemed.add(builtin)
101-
else
102-
builtins.add('node:' + builtin)
103-
}
104-
}
105-
106127
return {
107128
name: 'node-externals',
108129

109130
async buildStart() {
110131

111-
// Begin with the include option as it has precedence over the other options.
132+
// Begin with the include option as it has precedence over the other inclusion options.
112133
externals = [ ...include ]
113134

135+
// Add builtins
136+
if (config.builtins) {
137+
externals.push(
138+
new RegExp('^(?:' + _builtinModules.bare.filter(isNotExcluded).join('|') + ')$'),
139+
new RegExp('^node:(?:' + _builtinModules.schemed.filter(isNotExcluded).map(id => id.replace(nodePrefixRx, '')).join('|') + ')$'),
140+
)
141+
}
142+
114143
// Find and filter dependencies, supporting potential import from a sub directory (e.g. 'lodash/map').
115-
const packagePaths: string[] = ([] as string[]).concat(config.packagePath)
116-
const dependencies = (await findDependencies({
117-
packagePaths: packagePaths.length > 0 ? packagePaths : findPackagePaths(),
118-
keys: [
119-
config.deps && 'dependencies',
120-
config.devDeps && 'devDependencies',
121-
config.peerDeps && 'peerDependencies',
122-
config.optDeps && 'optionalDependencies'
123-
].filter(Boolean) as string[],
124-
warnings
125-
})).filter(isNotExcluded)
126-
127-
if (dependencies.length > 0) {
128-
externals.push(new RegExp('^(?:' + dependencies.join('|') + ')(?:/.+)?$'))
144+
if (config.deps || config.devDeps || config.peerDeps || config.optDeps) {
145+
const packagePaths: string[] = ([] as string[]).concat(config.packagePath)
146+
const dependencies = (await findDependencies({
147+
packagePaths: packagePaths.length > 0 ? packagePaths : findPackagePaths(),
148+
keys: [
149+
config.deps && 'dependencies',
150+
config.devDeps && 'devDependencies',
151+
config.peerDeps && 'peerDependencies',
152+
config.optDeps && 'optionalDependencies'
153+
].filter(Boolean) as string[],
154+
warnings
155+
})).filter(isNotExcluded)
156+
157+
if (dependencies.length > 0) {
158+
externals.push(new RegExp('^(?:' + dependencies.join('|') + ')(?:/.+)?$'))
159+
}
129160
}
130161

131162
// Issue the warnings we may have collected.
132-
let warning: string | undefined
133-
while ((warning = warnings.shift())) {
134-
this.warn(warning)
163+
while (warnings.length > 0) {
164+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
165+
this.warn(warnings.shift()!)
135166
}
136167
},
137168

138-
resolveId(importee) {
169+
resolveId(id) {
139170

140171
// Ignore already resolved ids, relative imports and virtual modules.
141-
if (path.isAbsolute(importee) || /^(?:\0|\.{1,2}[\\/])/.test(importee))
172+
if (path.isAbsolute(id) || /^(?:\0|\.{1,2}[\\/])/.test(id))
142173
return null
143174

144-
// Handle builtins first.
145-
if (alwaysSchemed.has(importee))
146-
return false
175+
// Check for externality.
176+
const external = isExternal(id) && isNotExcluded(id)
147177

148-
if (builtins.has(importee)) {
149-
if (config.prefixedBuiltins === false)
150-
return false
178+
// If not a builtin, or we're told not to handle prefixes, return status immediately.
179+
if (!builtins.has(id) || config._builtinsPrefix === 'ignore')
180+
return external ? false : null
151181

152-
const stripped = importee.replace(/^node:/, '')
153-
const prefixed = 'node:' + stripped
154-
155-
return config.prefixedBuiltins === 'strip'
156-
? { id: stripped, external: true }
157-
: { id: prefixed, external: true }
182+
// Otherwise, handle prefix.
183+
const stripped = id.replace(nodePrefixRx, '')
184+
return {
185+
id: alwaysSchemed.has(id) || config._builtinsPrefix === 'add'
186+
? 'node:' + stripped
187+
: stripped,
188+
external
158189
}
159-
160-
// Handle dependencies.
161-
return isExternal(importee) && isNotExcluded(importee)
162-
? false
163-
: null
164190
}
165191
}
166192
}

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"module": "CommonJS",
1818
"declaration": true,
1919
"declarationMap": false,
20+
"sourceMap": true,
2021
// "stripInternal": true,
2122

2223
// Type checkings

0 commit comments

Comments
 (0)