// rollup.config.ts
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import image from '@rollup/plugin-image';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';
import { getFiles } from '../../scripts/build-utils';
/**
* Generate rollup config by module
* @param {string} module module type to build, cjs or esm
* @return {any} rollup config
*/
function getConfig(module) {
const outputDir = module === 'cjs' ? 'dist' : `dist/${module}`;
const config = {
input: ['src/index.ts', ...getFiles('./src', ['.ts', '.tsx'], ['.test.tsx'])],
output: [
{
dir: outputDir,
format: module,
exports: 'named',
preserveModules: true,
preserveModulesRoot: 'src',
sourcemap: false
}
],
// the npm publish script will ignore the node_modules in the build files, and we need 'tslib`, so add it to external
external: ['clsx', 'tslib'],
plugins: [
peerDepsExternal({
includeDependencies: true
}),
resolve(),
commonjs(),
typescript({
tsconfig: './tsconfig.build.json',
declarationDir: outputDir
}),
json(),
postcss(),
image(),
terser()
]
};
return config;
}
const configs = ['cjs', 'esm'].map((module) => getConfig(module));
export default configs;
// tsconfig.build.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"rootDir": "src"
},
"include": ["./src/**/*.ts*"],
"exclude": ["src/**/*.test.ts*"]
}
// tsconfig.json
{
"extends": "../../tsconfig.json"
}
// ../../tsconfig.json
{
"compilerOptions": {
"module": "esnext",
"target": "es5",
"lib": ["es2020", "dom"],
"sourceMap": true,
"jsx": "react-jsx",
"esModuleInterop": true,
"strict": true,
"noEmit": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"noImplicitAny": false,
"resolveJsonModule": true
},
"exclude": ["dist", "node_modules", "scripts"],
"types": ["typePatches"]
}
// build-utils.js
const { readdirSync, lstatSync } = require('fs');
export const getFiles = (entry, extensions = [], excludeExtensions = []) => {
let fileNames = [];
const dirs = readdirSync(entry);
dirs.forEach((dir) => {
const path = `${entry}/${dir}`;
if (lstatSync(path).isDirectory()) {
fileNames = [...fileNames, ...getFiles(path, extensions, excludeExtensions)];
return;
}
if (!excludeExtensions.some((exclude) => dir.endsWith(exclude)) && extensions.some((ext) => dir.endsWith(ext))) {
fileNames.push(path);
}
});
return fileNames;
};
// frank-build.js
/* eslint-disable no-console */
const { resolve, join, basename } = require('path');
const { readFile, writeFile, copy } = require('fs-extra');
const { readdirSync } = require('fs');
const packagePath = process.cwd();
const buildFolder = './dist';
const distPath = join(packagePath, buildFolder);
const ignoreFolders = ['node_modules', 'util', 'common', 'cjs', 'locales', '__mocks__'];
const getFolders = (source) =>
readdirSync(source, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.filter((name) => ignoreFolders.indexOf(name) == -1);
const writeJson = (targetPath, obj) => writeFile(targetPath, JSON.stringify(obj, null, 2), 'utf8');
async function createPackageFile() {
const packageData = await readFile(resolve(packagePath, './package.json'), 'utf8');
const { scripts, devDependencies, ...packageOthers } = JSON.parse(packageData);
const newPackageData = {
...packageOthers,
sideEffects: false,
module: './esm/index.js',
main: './index.js',
types: './index.d.ts'
};
const targetPath = resolve(distPath, './package.json');
await writeJson(targetPath, newPackageData);
console.log(`Created package.json in ${targetPath}`);
}
async function createModulePackages() {
getFolders(buildFolder).map(async (folder) => {
const packageData = {
sideEffects: false,
module: `../esm/${folder}/index.js`,
main: './index.js',
types: './index.d.ts'
};
const targetPath = resolve(distPath, `./${folder}/package.json`);
await writeJson(targetPath, packageData);
console.log(`Created Module package.json in ${targetPath}`);
});
}
async function includeFileInBuild(file) {
const sourcePath = resolve(packagePath, file);
const targetPath = resolve(distPath, basename(file));
await copy(sourcePath, targetPath);
console.log(`Copied ${sourcePath} to ${targetPath}`);
}
async function run() {
try {
await createModulePackages();
await createPackageFile();
await includeFileInBuild('./README.md');
} catch (err) {
console.error(err);
process.exit(1);
}
}
run();
// package.json
{
"name": "@joseph/components",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"scripts": {
"build": "rimraf dist && rollup -c && yarn postbuild",
"postbuild": "node ../../scripts/frank-build.js",
"publish": "npm publish ./dist",
"prestart": "rimraf dist"
}
}