rollup.base.mjs (125 lines of code) (raw):
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { dts } from 'rollup-plugin-dts';
import commonjs from '@rollup/plugin-commonjs';
import swc from '@rollup/plugin-swc';
import { readFileSync } from 'node:fs';
import { basename, dirname, format, parse, relative, resolve } from 'node:path';
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'));
const cwd = process.cwd();
const deps = new Set([...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})]);
export default createConfig('build/index.js', { dts: true });
export function createConfig(input, { banner, external = [], dts } = {}) {
const bundles = [bundleJs(input, { banner, external })];
if (dts) {
if (typeof dts === 'boolean') {
dts = format({ ...parse(input), base: undefined, ext: '.d.ts' });
// console.log('dts:', dts);
}
bundles.push(bundleDts(dts));
}
return bundles;
}
export function bundleDts(input) {
const dir = dirname(input) + '/';
const name = basename(input, '.d.ts');
return {
input,
external: createExternalTest(dir),
output: [
{
file: `dist/${name}.d.ts`,
},
],
plugins: [dts({ respectExternal: true })],
};
}
export function bundleJs(input, { banner, external = [] } = {}) {
const dir = dirname(input) + '/';
const name = basename(input, '.js');
return {
input,
external: createExternalTest(dir, external),
output: [
{
file: `dist/${name}.mjs`,
banner,
format: 'es',
sourcemap: true,
generatedCode: 'es2015',
},
{
file: `dist/${name}.cjs`,
banner,
format: 'cjs',
sourcemap: true,
generatedCode: 'es2015',
},
],
plugins: [
nodeResolve(),
commonjs(),
swc({
swc: {
jsc: {
target: 'es2018',
},
},
}),
],
};
}
function createExternalTest(prefix, external = []) {
return (id, parent) => {
// this function decides if a module should be bundled or not (external)
// a module is not bundled if it points to one of our dependencies
// if it's not a dependency but still resolves outside the build/esm dir, an error is thrown
const path = normalizePath(id, parent);
const depId = npmPackage(id);
if (deps.has(depId)) {
console.log('exclude:', path);
return true;
}
if (path.startsWith(pkg.name)) {
console.log('exclude:', path);
return true;
}
if (!path.startsWith(prefix)) {
// this will throw if we accidentally import something that isn't local and wasn't listed among dependencies
throw new Error(`Attempt to bundle external dependency ${path} for import ${id}. Are we missing a dependency?`);
}
if (external.includes(path.slice(prefix.length))) {
console.log('exclude:', path);
return true;
}
// console.log('include:', path);
return false;
};
}
/**
* Return the path relative to cwd for a give import. Npm package imports will be returned as if they were directly in cwd.
*
* @param id the imported path
* @param parent absolute path of the file containing the import, or undefined if it's the entry point
* @returns path relative to cwd
*/
function normalizePath(id, parent = '') {
const absolute = id.startsWith('.') ? resolve(parent, id) : id;
return relative(cwd, absolute);
}
/**
* Return the npm package name of an import, if applicable.
*
* @param id an import path
* @returns the npm package name if id is determined to point to an npm package, '' otherwise.
*/
function npmPackage(id) {
// relative or absolute ids are not npm packages
if (id.startsWith('.') || id.startsWith('/')) return '';
// we're only ever interested in the first two parts the id (the second is needed if the first part specifies a scope)
const parts = id.split('/', 2);
if (parts[0].startsWith('@')) {
// it's a scoped package, so it will include one backslash
return parts.join('/');
}
return parts[0];
}