Skip to content

Commit 5330005

Browse files
committed
feat: add --preserve-modules flag for tree-shaking support
1 parent ddf5b86 commit 5330005

File tree

5 files changed

+83
-20
lines changed

5 files changed

+83
-20
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.2.0] - 2025-12-15
11+
12+
### Added
13+
14+
- 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).
15+
1016
## [0.1.2] - 2022-06-05
1117

1218
### Fixed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,36 @@ NOTICE: this package doesn't clean your build directories in each run, so you'd
2323

2424
- `-w` to watch the files.
2525
- `-p` to define a different `tsconfig.ts` file. p.eg: `package-build -p tsconfig.build.json`.
26+
- `-m` or `--preserve-modules` to enable tree-shaking optimization (see below).
27+
28+
#### Tree Shaking Optimization
29+
30+
For libraries with many exports (like icon libraries), use the `--preserve-modules` flag to generate individual files instead of a single bundle:
31+
32+
```bash
33+
package-build --preserve-modules
34+
package-build -m
35+
```
36+
37+
This enables effective tree-shaking for consumers using modern bundlers. When enabled:
38+
39+
- Output files are generated in `dist/` (CJS) and `es2015/` (ESM) directories
40+
- The source directory structure is preserved
41+
- Each module becomes a separate file instead of being bundled into one
42+
43+
**Recommended package.json configuration for tree-shakable libraries:**
44+
45+
```json
46+
{
47+
"sideEffects": false,
48+
"exports": {
49+
".": {
50+
"import": "./es2015/index.js",
51+
"require": "./dist/index.js"
52+
}
53+
}
54+
}
55+
```
2656

2757
### Publish a new version
2858

build.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,16 @@ const watch = (config) => {
3333
watcher.on('event', (event) => eventHandler(event));
3434
};
3535

36-
const build = async (withWatch = false, project) => {
36+
const build = async (withWatch = false, project, options = {}) => {
3737
const dirname = process.cwd();
38+
const { preserveModules = false } = options;
39+
3840
console.log(`Bundling ${dirname}`);
39-
const { input, output } = getRollupConfig(dirname, project);
41+
if (preserveModules) {
42+
console.log('Using preserveModules mode for tree-shaking optimization');
43+
}
44+
45+
const { input, output } = getRollupConfig(dirname, project, options);
4046

4147
if (withWatch) {
4248
watch({ ...input, output });
@@ -45,7 +51,8 @@ const build = async (withWatch = false, project) => {
4551
const bundle = await rollup.rollup(input);
4652
await Promise.all(
4753
output.map(async (outputItem) => {
48-
console.log(`Bundling ${outputItem.format} into ${outputItem.file}`);
54+
const target = outputItem.dir || outputItem.file;
55+
console.log(`Bundling ${outputItem.format} into ${target}`);
4956
return bundle.write(outputItem);
5057
}),
5158
);
@@ -60,4 +67,8 @@ const build = async (withWatch = false, project) => {
6067

6168
const argv = minimist(process.argv.slice(2));
6269

63-
build(argv.w, argv.p);
70+
const options = {
71+
preserveModules: argv.m || argv['preserve-modules'] || false,
72+
};
73+
74+
build(argv.w, argv.p, options);

getRollupConfig.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const compileTypings = (cwd) => () => {
1515
});
1616
};
1717

18-
module.exports = (dirname, project) => {
18+
module.exports = (dirname, project, options = {}) => {
19+
const { preserveModules = false } = options;
1920
const pkgPath = path.join(dirname, 'package.json');
2021
// eslint-disable-next-line import/no-dynamic-require, global-require
2122
const pkg = require(pkgPath);
@@ -27,6 +28,31 @@ module.exports = (dirname, project) => {
2728

2829
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
2930

31+
const getOutputConfig = (format) => {
32+
const baseConfig = {
33+
sourcemap: true,
34+
interop: 'auto',
35+
};
36+
37+
if (preserveModules) {
38+
return {
39+
...baseConfig,
40+
dir: format === 'cjs' ? 'dist' : 'es2015',
41+
format,
42+
preserveModules: true,
43+
preserveModulesRoot: 'src',
44+
entryFileNames: '[name].js',
45+
};
46+
}
47+
48+
// Legacy mode: single bundle file
49+
return {
50+
...baseConfig,
51+
file: format === 'cjs' ? pkg.main : pkg.module,
52+
format,
53+
};
54+
};
55+
3056
return {
3157
input: {
3258
input: `${dirname}/src/index.ts`,
@@ -60,19 +86,6 @@ module.exports = (dirname, project) => {
6086
}),
6187
],
6288
},
63-
output: [
64-
{
65-
file: pkg.main,
66-
format: 'cjs',
67-
sourcemap: true,
68-
// See: https://rollupjs.org/configuration-options/#output-interop
69-
interop: 'auto',
70-
},
71-
{
72-
file: pkg.module,
73-
format: 'esm',
74-
sourcemap: true,
75-
},
76-
],
89+
output: [getOutputConfig('cjs'), getOutputConfig('esm')],
7790
};
7891
};

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"bin": {
99
"package-build": "./bin/runner.js"
1010
},
11+
"engines": {
12+
"node": ">=20.0.0"
13+
},
1114
"scripts": {
1215
"build": "echo 'No build script required.'",
1316
"test": "npm run lint:check && npm run format:check",
@@ -52,7 +55,7 @@
5255
"typescript": ">= 3.7"
5356
},
5457
"volta": {
55-
"node": "18.16.0",
58+
"node": "20.18.1",
5659
"yarn": "1.22.19"
5760
},
5861
"publishConfig": {

0 commit comments

Comments
 (0)