diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c7c70..1561605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.0] - 2025-12-15 + +### Added + +- New `--preserve-modules` (or `-m`) flag to enable tree-shaking optimization. When enabled, Rollup generates individual files preserving the source directory structure instead of a single monolithic bundle. This allows consumers using modern bundlers to effectively tree-shake unused exports, significantly reducing bundle sizes for libraries with many exports (e.g., icon libraries). + ## [0.1.2] - 2022-06-05 ### Fixed diff --git a/README.md b/README.md index 7cb6643..0fdbb45 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,36 @@ NOTICE: this package doesn't clean your build directories in each run, so you'd - `-w` to watch the files. - `-p` to define a different `tsconfig.ts` file. p.eg: `package-build -p tsconfig.build.json`. +- `-m` or `--preserve-modules` to enable tree-shaking optimization (see below). + +#### Tree Shaking Optimization + +For libraries with many exports (like icon libraries), use the `--preserve-modules` flag to generate individual files instead of a single bundle: + +```bash +package-build --preserve-modules +package-build -m +``` + +This enables effective tree-shaking for consumers using modern bundlers. When enabled: + +- Output files are generated in `dist/` (CJS) and `es2015/` (ESM) directories +- The source directory structure is preserved +- Each module becomes a separate file instead of being bundled into one + +**Recommended package.json configuration for tree-shakable libraries:** + +```json +{ + "sideEffects": false, + "exports": { + ".": { + "import": "./es2015/index.js", + "require": "./dist/index.js" + } + } +} +``` ### Publish a new version diff --git a/build.js b/build.js index 22d6b86..77701e8 100755 --- a/build.js +++ b/build.js @@ -33,10 +33,16 @@ const watch = (config) => { watcher.on('event', (event) => eventHandler(event)); }; -const build = async (withWatch = false, project) => { +const build = async (withWatch = false, project, options = {}) => { const dirname = process.cwd(); + const { preserveModules = false } = options; + console.log(`Bundling ${dirname}`); - const { input, output } = getRollupConfig(dirname, project); + if (preserveModules) { + console.log('Using preserveModules mode for tree-shaking optimization'); + } + + const { input, output } = getRollupConfig(dirname, project, options); if (withWatch) { watch({ ...input, output }); @@ -45,7 +51,8 @@ const build = async (withWatch = false, project) => { const bundle = await rollup.rollup(input); await Promise.all( output.map(async (outputItem) => { - console.log(`Bundling ${outputItem.format} into ${outputItem.file}`); + const target = outputItem.dir || outputItem.file; + console.log(`Bundling ${outputItem.format} into ${target}`); return bundle.write(outputItem); }), ); @@ -60,4 +67,8 @@ const build = async (withWatch = false, project) => { const argv = minimist(process.argv.slice(2)); -build(argv.w, argv.p); +const options = { + preserveModules: argv.m || argv['preserve-modules'] || false, +}; + +build(argv.w, argv.p, options); diff --git a/getRollupConfig.js b/getRollupConfig.js index 67ae7b0..6639e44 100644 --- a/getRollupConfig.js +++ b/getRollupConfig.js @@ -15,7 +15,8 @@ const compileTypings = (cwd) => () => { }); }; -module.exports = (dirname, project) => { +module.exports = (dirname, project, options = {}) => { + const { preserveModules = false } = options; const pkgPath = path.join(dirname, 'package.json'); // eslint-disable-next-line import/no-dynamic-require, global-require const pkg = require(pkgPath); @@ -27,6 +28,31 @@ module.exports = (dirname, project) => { const extensions = ['.js', '.jsx', '.ts', '.tsx']; + const getOutputConfig = (format) => { + const baseConfig = { + sourcemap: true, + interop: 'auto', + }; + + if (preserveModules) { + return { + ...baseConfig, + dir: format === 'cjs' ? 'dist' : 'es2015', + format, + preserveModules: true, + preserveModulesRoot: 'src', + entryFileNames: '[name].js', + }; + } + + // Legacy mode: single bundle file + return { + ...baseConfig, + file: format === 'cjs' ? pkg.main : pkg.module, + format, + }; + }; + return { input: { input: `${dirname}/src/index.ts`, @@ -60,19 +86,6 @@ module.exports = (dirname, project) => { }), ], }, - output: [ - { - file: pkg.main, - format: 'cjs', - sourcemap: true, - // See: https://rollupjs.org/configuration-options/#output-interop - interop: 'auto', - }, - { - file: pkg.module, - format: 'esm', - sourcemap: true, - }, - ], + output: [getOutputConfig('cjs'), getOutputConfig('esm')], }; }; diff --git a/package.json b/package.json index 1bb6886..7d5061b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cabify/package-build", - "version": "0.1.1", + "version": "0.2.1-beta.0", "description": "Common configuration & scripts for building TS packages with rollup", "license": "Apache-2.0", "main": "build.js", @@ -8,6 +8,9 @@ "bin": { "package-build": "./bin/runner.js" }, + "engines": { + "node": ">=20.0.0" + }, "scripts": { "build": "echo 'No build script required.'", "test": "npm run lint:check && npm run format:check", @@ -52,7 +55,7 @@ "typescript": ">= 3.7" }, "volta": { - "node": "18.16.0", + "node": "20.18.1", "yarn": "1.22.19" }, "publishConfig": {