Reputation: 169
With lodash-es, we can cherry-pick modules like this: import drop from 'lodash/drop';
This way only drop
gets used and nothing else.
How do I achieve the same thing with Rollup?
I'm configuring a react & typescript ui library to be used in two create-react-app projects. I learned that using esm is best for my use case and it's the most affective way to achieve treeshaking with Rollup. However, when I use my library, I'm importing like this: import { someComponent }from 'my-ui-library';
is this singling out someComponent
like the cherry-pick method above. Does the syntax matter?
Here is my rollup.config.js
:
import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
import svgr from '@svgr/rollup';
import url from '@rollup/plugin-url';
import json from '@rollup/plugin-json';
import external from 'rollup-plugin-peer-deps-external';
import includePaths from 'rollup-plugin-includepaths';
const pkg = require('./package.json');
export default {
cache: false,
input: 'src/index.ts',
output: [{ file: pkg.module, format: 'esm' }],
treeshake: true,
plugins: [
external({
preferBuiltins: false,
}),
babel({
exclude: /node_modules/,
plugins: ['external-helpers'],
externalHelpers: true, // Exclude babel helpers.
}),
json(),
resolve(),
svgr({
ref: true,
svgo: false,
}),
typescript({
clean: true,
typescript: require('typescript'),
}),
url(),
includePaths({
paths: ['src'],
}),
],
};
Upvotes: 1
Views: 1497
Reputation: 19762
There seems to be a few catches when tree-shaking ESM packages. The first catch is that tree shaking only works in production and the second catch is that your package.json
must contain a sideEffects: false
property. Unfortunately, in development, tree shaking still imports the entire library to retrieve the named export. Click here more info about webpack tree-shaking.
For example (in development): import screenshot exmaple
In the example above, I'm importing a named export called defaultLayoutCommands
; however, it still shows that all of the library was imported, even though webpack is utilizing the esm
file. However, if I separate the file and import that directly, then it shows the smaller import size. In production, this should tree-shake import the defaultLayoutCommands
and be just like the second import example. That said, tree-shaking only works with webpack AND ESM.
When working with CJS/UMD/AMD, tree-shaking won't work. Instead, you'll have to combine all of your exports to a single class/object (like an exports
object). Then using ES6 destructuring, you can pull off that method.
For example, lodash has all the functions within a single file, for example:
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
});
}
And then attaches that function as a method to an object:
lodash.add = add;
lodash.attempt = attempt;
lodash.camelCase = camelCase;
...
lodash.throttle = throttle;
lodash.thru = thru;
lodash.toArray = toArray;
...etc
And then using an IIFE exports the lodash
object. The nice thing about this is that you can require multiple named imports at once:
const { get, debounce, isEmpty } = require("lodash");
The disadvantage is that you're requiring the entire lodash object. So, to get around this, they have separate files (and/or npm packages!!!) to limit the imports to just what's needed. So, they have included additional files like this in the root package folder:
...js
get.js
debounce.js
isEmpty.js
...js
However, the disadvantage with this, is that you'll now have three imports:
const get = require("lodash/get");
const debounce = require("lodash/debounce");
const isEmpty = require("lodash/isEmpty");
But! You'll have an overall smaller import size... however, the package size is larger! So to mitigate this, they broke up some of the popular methods into their own separate packages: lodash.get, lodash.debounce, and lodash.isEmpty!
So, in summation, if you don't plan on utilizing CJS/UMD/AMD (like lodash does), then tree-shaking will work as expected in production when using webpack and ESM packages within the client. However, if you are including additional builds for more compatibility, then you'll have to create an array of rollup configs with different inputs
and different outputs
.
For example:
const resolutions = {
globals: {
react: "React",
"react-is": "reactIs",
},
exports: "named",
};
export default [
{
input: "./src/index.js", // this transforms the entire source
output: [
{
file: pkg.main,
format: "cjs",
...resolutions,
},
{
file: pkg.fallback,
format: "umd",
name: "My-Example-Library",
...resolutions,
},
{
file: pkg.module,
format: "esm",
...resolutions,
},
],
...etc
},
{
input: "./src/components/example.js", // this transforms just an "example.js" file and ouputs to a "dist/example.[build].js" file
output: [
{
file: "dist/example.cjs.js,
format: "cjs",
...resolutions,
},
{
file: dist/example.umd.js,
format: "umd",
name: "My-Example",
...resolutions,
},
],
...etc
}
]
More info about use cases for the different builds can be found here and an example Rollup config can be found here.
Upvotes: 4