Writing an NPM package containing es6 modules, is it best practice to keep the source files separate
package.json
esm
index.js
Content1
Content1A.js
Content1A.js.map
Content1B.js
Content1B.js.map
Content2
Content2A.js
Content2A.js.map
Content2B.js
Content2B.js.map
with index.js
referencing contents in subfolders, or is it better practice to bundle it into one file
package.json
esm
contents.js
contents.js.map
Seems the first method has an advantage with CommonJS modules since it gives a consumer possibility to import directly from the source and thus skip unused imports from index.js
(since CommonJS modules are not tree-shakeable) but with es6 modules, this argument disappears.
Different bundlers might be capable of different things. The rest of this answer refers to Webpack which, being one of the most common bundlers, should influence decisions in this area.
The most important factor governing the decision about whether to bundle your library or not should be related to tree-shaking. No other important aspects come to mind for me.
sideEffects: false
Setting in package.json
that indicates whether modules in the package have side effects which needs to be executed when the module is imported but not consumed. Setting it to false
indicates that no modules have side effects. May also be set to a list of modules which have side effects and other more complex values. Default seem to be true
indicating that all modules have side effects.
This parameters plays a large role when using an entrypoint index in your package, from which all package exports are re-exported. Sparse imports from this index could easily cause your entire package to be bundled if this setting is not correct.
optimization.usedExports: true
Setting in webpack.config.js
indicating to Webpack that all exports that are not used may be excluded. This activates a heuristic used by Terser to remove unused code inside a module. Is set to true
by default.
In toy scenarios, this setting might seem efficient enough and the sideEffects
flag might not seem to play a big role. This is not the case in real scenarios with more complex code where it is harder for this heuristic to do a good job.
/*#__PURE__*/
Annotation to be used before statements (such as functions) to indicate that they can be excluded if not explicitly used. These annotations also play a part in the heuristic used by Terser to remove unused code inside a module.
To allow your consumers to benefit the most from tree-shaking, it seems advisable to not bundle your es6 npm package and instead let the separate input modules remain separate so that the sideEffects
setting in package.json
may result in the consumer bundler to prune as many unused modules as possible. Rely on optimization.usedExports
inside modules, evaluate bundle content and add /*#__PURE__*/
annotations where you think it could make a big difference. If everything is bundled in the same file, the sideEffects
flag in package.json
can't do the main part of the job as everything is in the same module and subsequently we have to rely on a lot of additional /*#__PURE__*/
annotations and heuristics in the consumer bundler to make tree-shaking as efficient as possible, which requires more from you (in terms of annotations) and does not come with any particular advantage. Remember to build your package in production mode as optimizations are not always active otherwise.