rollup-plugin-node-externals
Version:
Automatically declare NodeJS built-in modules and npm dependencies as 'external' in Rollup/Vite config
169 lines • 7.13 kB
JavaScript
import assert from 'node:assert';
import path from 'node:path';
import fs from 'node:fs/promises';
import cp from 'node:child_process';
import { isBuiltin } from 'node:module';
import self from '#package.json' with { type: 'json' };
const monorepoRootFiles = [
'pnpm-workspace.yaml',
'lerna.json',
'rush.json',
];
const emptyArray = [];
function nodeExternals(options = {}) {
let config = undefined;
return {
name: self.name.replace(/^rollup-plugin-/, ''),
version: self.version,
apply: 'build',
enforce: 'pre',
async buildStart() {
config ??= await getConfig.call(this);
config.packages.forEach(pkg => this.addWatchFile(pkg));
},
watchChange(id) {
if (config?.packages.has(id))
config = undefined;
},
resolveId(specifier, _, { isEntry }) {
if (isEntry
|| /^(?:\0|\.{1,2}\/)/.test(specifier)
|| path.isAbsolute(specifier)) {
return null;
}
assert(config);
if (isBuiltin(specifier)) {
const stripped = specifier.replace(/^node:/, '');
const prefixed = 'node:' + stripped;
return {
id: config.prefix ? prefixed
: config.prefix === false ? isBuiltin(stripped) ? stripped : prefixed
: specifier,
external: (config.builtins || config.isIncluded(specifier)) && !config.isExcluded(specifier),
moduleSideEffects: false
};
}
return config.isIncluded(specifier) && !config.isExcluded(specifier)
? false
: null;
}
};
async function getConfig() {
const config = {
builtins: true,
prefix: true,
packages: new Set(),
deps: true,
peerDeps: true,
optDeps: true,
devDeps: false,
include: [],
exclude: [],
isIncluded,
isExcluded
};
for (const [key, value] of Object.entries(options)) {
switch (key) {
case 'include':
case 'exclude':
emptyArray.concat(value).forEach((entry, index) => {
if (entry instanceof RegExp)
config[key].push(entry);
else if (isString(entry))
config[key].push(new RegExp('^' + RegExp.escape(entry) + '$'));
else if (entry)
this.warn(`Ignoring wrong entry type #${index} in '${key}' option: ${JSON.stringify(entry)}.`);
});
continue;
case 'packagePath':
emptyArray.concat(value).forEach((entry, index) => {
if (isString(entry))
config.packages.add(path.resolve(entry));
else if (entry)
this.warn(`Ignoring wrong entry type #${index} in '${key}' option: ${JSON.stringify(entry)}.`);
});
continue;
case 'builtinsPrefix':
if (value === 'add' || value === true)
config.prefix = true;
else if (value === 'strip' || value === false)
config.prefix = false;
else if (value === 'ignore')
config.prefix = null;
else
this.warn(`Ignoring bad value ${JSON.stringify(value)} for option '${key}', using default of 'add'.`);
continue;
case 'builtins':
case 'deps':
case 'devDeps':
case 'optDeps':
case 'peerDeps':
config[key] = Boolean(value);
continue;
default:
this.warn(`Ignoring unknown option ${JSON.stringify(key)}.`);
continue;
}
}
if (config.packages.size === 0) {
const gitTopLevel = await new Promise(resolve => {
cp.execFile('git', ['rev-parse', '--show-toplevel'], (error, stdout) => resolve(error ? typeof error.code === 'string' ? undefined : null
: path.normalize(stdout.trim())));
});
walk: for (const cwd of walkUp(process.cwd())) {
let file = path.join(cwd, 'package.json');
if (await fileExists(file))
config.packages.add(file);
if (cwd === gitTopLevel || (gitTopLevel === undefined && await directoryExists(path.join(cwd, '.git'))))
break;
for (file of monorepoRootFiles) {
if (await fileExists(path.join(cwd, file)))
break walk;
}
}
}
const externalDependencies = {};
for (const pkg of config.packages) {
const json = await fs.readFile(pkg).then(buffer => JSON.parse(buffer.toString()), (err) => err);
if (json instanceof Error) {
const message = json instanceof SyntaxError
? `File ${JSON.stringify(pkg)} does not look like a valid package.json.`
: `Cannot read ${JSON.stringify(pkg)}, error: ${json.code}.`;
this.error({ message, cause: json });
}
Object.assign(externalDependencies, config.deps ? json.dependencies : undefined, config.devDeps ? json.devDependencies : undefined, config.peerDeps ? json.peerDependencies : undefined, config.optDeps ? json.optionalDependencies : undefined);
if (Array.isArray(json.workspaces))
break;
}
const names = Object.keys(externalDependencies);
if (names.length > 0)
config.include.push(new RegExp('^(?:' + names.map(RegExp.escape).join('|') + ')(?:/.+)?$'));
return config;
function isString(str) {
return typeof str === 'string' && str.length > 0;
}
function* walkUp(from) {
let previous = '';
while (previous !== from) {
yield from;
previous = from;
from = path.dirname(from);
}
}
function fileExists(name) {
return fs.stat(name).then(stat => stat.isFile(), () => false);
}
function directoryExists(name) {
return fs.stat(name).then(stat => stat.isDirectory(), () => false);
}
function isIncluded(id) {
return this.include.length > 0 && this.include.some(rx => rx.test(id));
}
function isExcluded(id) {
return this.exclude.length > 0 && this.exclude.some(rx => rx.test(id));
}
}
}
export default nodeExternals;
export { nodeExternals };
//# sourceMappingURL=index.js.map