Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 15 additions & 4 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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);
}),
);
Expand All @@ -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);
43 changes: 28 additions & 15 deletions getRollupConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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`,
Expand Down Expand Up @@ -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')],
};
};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"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",
"module": "build.js",
"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",
Expand Down Expand Up @@ -52,7 +55,7 @@
"typescript": ">= 3.7"
},
"volta": {
"node": "18.16.0",
"node": "20.18.1",
"yarn": "1.22.19"
},
"publishConfig": {
Expand Down
Loading