为什么我的 React 组件库不可摇树优化?
Why is my React component library not tree-shakable?
我有一个 React component library 与 rollup 捆绑在一起。然后,我在一个应用程序设置中使用该库,该库使用 create-react-app,它在后台使用 Webpack。我希望 Webpack 对组件库进行 tree-shaking。在构建应用程序包并对其进行分析后,我发现该库要么根本没有经过 tree-shaking,要么 tree-shaking 在该库上不起作用,因为它首先不可进行 tree-shaking。为什么 tree-shaking 不起作用?我做错了什么?
rollup.config.js(React组件库的bundler配置)
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import autoExternal from 'rollup-plugin-auto-external'
import resolve from 'rollup-plugin-node-resolve'
import reactSvg from 'rollup-plugin-react-svg'
import url from 'rollup-plugin-url'
import string from 'rollup-plugin-string'
import pureanno from 'rollup-plugin-pure-annotation'
import pkg from './package.json'
const { getSVGOConfig } = require('./scripts/getSVGOConfig')
const MAX_INLINE_FILE_SIZE_KB = 100
export default {
input: 'src/index.js',
output: [
{
file: pkg.module,
format: 'es',
},
],
plugins: [
autoExternal(),
babel({
babelrc: false,
exclude: 'node_modules/**',
plugins: [
'external-helpers',
'babel-plugin-transform-react-jsx',
'babel-plugin-transform-class-properties',
'babel-plugin-transform-object-rest-spread',
'transform-react-remove-prop-types',
[
'babel-plugin-root-import',
{
'rootPathSuffix': 'src',
},
],
'babel-plugin-styled-components',
'transform-decorators-legacy',
[
'ramda',
{
'useES': true,
},
],
],
}),
resolve(),
commonjs(),
reactSvg({
svgo: getSVGOConfig(),
}),
url({
limit: MAX_INLINE_FILE_SIZE_KB * 1024,
include: ['**/*.woff', '**/*.woff2'],
}),
string({
include: '**/*.css',
}),
pureanno({
includes: ['**/*.js'],
}),
],
watch: {
chokidar: false,
},
}
React组件库src/index.js
export { default as Theme } from './Theme'
export { default as Badge } from './components/Badge'
...
App.js(使用库的应用)
import React from 'react';
import { Theme, Badge } from 'my-react-component-library'
function App() {
return (
<Theme>
<Badge>Hello</Badge>
</Theme>
)
}
export default App
package.json React组件库(相关部分)
{
"name": "my-react-component-library",
"version": "1.1.1",
"main": "dist/index.js",
"module": "dist/index.es.js",
"scripts": {
...
"build": "rollup -c",
},
"dependencies": {
...
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
...
},
"sideEffects": false
}
使用库的应用程序的 package.json(相关部分)
{
"name": "my-app",
"version": "0.1.0",
"dependencies": {
"my-react-component-library": "^1.1.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"scripts": {
...
"analyze": "source-map-explorer build/static/js/*chunk*.js build/static/js/*chunk*.js.map",
"build": "react-scripts build",
"serve": "serve -s build"
},
"devDependencies": {
...
"serve": "^11.3.0",
"source-map-explorer": "^2.2.2"
}
}
index.es.js(捆绑的反应组件库)
https://gist.github.com/borisdiakur/ae376738955f15fb5079b5acb2ac83ad
我找到了一种可能的解决方案。不过,它与摇树无关。我只是 将库拆分成几个独立的块 通过使用 rollup 的一个相当新的功能(我必须升级一堆依赖项才能使其工作)并提供一个对象,将名称映射到入口点,到汇总配置的输入 属性。它看起来像这样:
input: {
index: 'src/index.js',
theme: 'src/Theme',
badge: 'src/components/Badge',
contentCard: 'src/components/ContentCard',
card: 'src/elements/Card',
icon: 'src/elements/Icon',
...
这里是 rollup 的文档:https://rollupjs.org/guide/en/#input
输出设置到一个目录:
output: [
{
dir: 'dist/es',
format: 'es',
},
],
然后我在 package.json 中声明入口点如下:
"module": "dist/es/index.js",
在我的测试应用程序中,我导入了组件,就好像没有任何变化一样:
import React from 'react';
import { Theme, Badge } from 'my-react-component-library'
到目前为止这似乎有效,虽然它又不是 tree-shaking,我仍然想知道如何使我的组件库可 tree-shaking。
更新:
原来 tree shaking 一直有效!这是图书馆的“错误”:
- Icon 组件导入了所有图标,因此只要您至少使用了一个图标或使用了一个图标的组件,所有 svg 文件就会在捆绑包中结束。
- Theme 组件将字体作为 base-64 字符串内联到包中。
我通过在需要时动态导入每个图标解决了第一个问题,通过减少 rollup-plugin-url 的 MAX_INLINE_FILE_SIZE_KB 参数解决了第二个问题,以便拆分字体并拥有它作为资产加载。
所以,对于像我一样开始相信 tree-shaking 不起作用的人,这是我的建议,只是因为 bundle 大得离谱:仔细检查你的 bundle 分析报告(即使用 source-map-explorer ), 寻找大人物并仔细检查你的进口。
您朝着正确的方向迈出了一步。 Tree shaking 在文件边界上起作用,它会丢弃导入路径中未使用的文件并且没有副作用。
您的第一个示例无法使用 tree shaking,因为您的整个包都捆绑在一个文件中。
您的第二个示例捆绑到多个文件中,但您的主捆绑包(带有 index.js
输入)仍然是一个巨大的文件,它将所有内容捆绑在一起,而不是将 imports
单独留在其中。 (这只是基于您发布的构建过程的假设,请检查您的 index.js
包以验证这一点)。
您必须:
定义index.jsas externals的依赖。尝试将整个 /src
目录添加为外部目录。
或
在构建过程中使用 babel 而不是 rollup 以保持导出和导入的完整性。
如果您有这样的问题,只需检查 material UI build process 和最终构建文件夹(npm i @material-ui/core
并查看 node_modules)。这是解决方案的一个很好的灵感。
我有一个 React component library 与 rollup 捆绑在一起。然后,我在一个应用程序设置中使用该库,该库使用 create-react-app,它在后台使用 Webpack。我希望 Webpack 对组件库进行 tree-shaking。在构建应用程序包并对其进行分析后,我发现该库要么根本没有经过 tree-shaking,要么 tree-shaking 在该库上不起作用,因为它首先不可进行 tree-shaking。为什么 tree-shaking 不起作用?我做错了什么?
rollup.config.js(React组件库的bundler配置)
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import autoExternal from 'rollup-plugin-auto-external'
import resolve from 'rollup-plugin-node-resolve'
import reactSvg from 'rollup-plugin-react-svg'
import url from 'rollup-plugin-url'
import string from 'rollup-plugin-string'
import pureanno from 'rollup-plugin-pure-annotation'
import pkg from './package.json'
const { getSVGOConfig } = require('./scripts/getSVGOConfig')
const MAX_INLINE_FILE_SIZE_KB = 100
export default {
input: 'src/index.js',
output: [
{
file: pkg.module,
format: 'es',
},
],
plugins: [
autoExternal(),
babel({
babelrc: false,
exclude: 'node_modules/**',
plugins: [
'external-helpers',
'babel-plugin-transform-react-jsx',
'babel-plugin-transform-class-properties',
'babel-plugin-transform-object-rest-spread',
'transform-react-remove-prop-types',
[
'babel-plugin-root-import',
{
'rootPathSuffix': 'src',
},
],
'babel-plugin-styled-components',
'transform-decorators-legacy',
[
'ramda',
{
'useES': true,
},
],
],
}),
resolve(),
commonjs(),
reactSvg({
svgo: getSVGOConfig(),
}),
url({
limit: MAX_INLINE_FILE_SIZE_KB * 1024,
include: ['**/*.woff', '**/*.woff2'],
}),
string({
include: '**/*.css',
}),
pureanno({
includes: ['**/*.js'],
}),
],
watch: {
chokidar: false,
},
}
React组件库src/index.js
export { default as Theme } from './Theme'
export { default as Badge } from './components/Badge'
...
App.js(使用库的应用)
import React from 'react';
import { Theme, Badge } from 'my-react-component-library'
function App() {
return (
<Theme>
<Badge>Hello</Badge>
</Theme>
)
}
export default App
package.json React组件库(相关部分)
{
"name": "my-react-component-library",
"version": "1.1.1",
"main": "dist/index.js",
"module": "dist/index.es.js",
"scripts": {
...
"build": "rollup -c",
},
"dependencies": {
...
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
...
},
"sideEffects": false
}
使用库的应用程序的 package.json(相关部分)
{
"name": "my-app",
"version": "0.1.0",
"dependencies": {
"my-react-component-library": "^1.1.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"scripts": {
...
"analyze": "source-map-explorer build/static/js/*chunk*.js build/static/js/*chunk*.js.map",
"build": "react-scripts build",
"serve": "serve -s build"
},
"devDependencies": {
...
"serve": "^11.3.0",
"source-map-explorer": "^2.2.2"
}
}
index.es.js(捆绑的反应组件库)
https://gist.github.com/borisdiakur/ae376738955f15fb5079b5acb2ac83ad
我找到了一种可能的解决方案。不过,它与摇树无关。我只是 将库拆分成几个独立的块 通过使用 rollup 的一个相当新的功能(我必须升级一堆依赖项才能使其工作)并提供一个对象,将名称映射到入口点,到汇总配置的输入 属性。它看起来像这样:
input: {
index: 'src/index.js',
theme: 'src/Theme',
badge: 'src/components/Badge',
contentCard: 'src/components/ContentCard',
card: 'src/elements/Card',
icon: 'src/elements/Icon',
...
这里是 rollup 的文档:https://rollupjs.org/guide/en/#input
输出设置到一个目录:
output: [
{
dir: 'dist/es',
format: 'es',
},
],
然后我在 package.json 中声明入口点如下:
"module": "dist/es/index.js",
在我的测试应用程序中,我导入了组件,就好像没有任何变化一样:
import React from 'react';
import { Theme, Badge } from 'my-react-component-library'
到目前为止这似乎有效,虽然它又不是 tree-shaking,我仍然想知道如何使我的组件库可 tree-shaking。
更新:
原来 tree shaking 一直有效!这是图书馆的“错误”:
- Icon 组件导入了所有图标,因此只要您至少使用了一个图标或使用了一个图标的组件,所有 svg 文件就会在捆绑包中结束。
- Theme 组件将字体作为 base-64 字符串内联到包中。
我通过在需要时动态导入每个图标解决了第一个问题,通过减少 rollup-plugin-url 的 MAX_INLINE_FILE_SIZE_KB 参数解决了第二个问题,以便拆分字体并拥有它作为资产加载。
所以,对于像我一样开始相信 tree-shaking 不起作用的人,这是我的建议,只是因为 bundle 大得离谱:仔细检查你的 bundle 分析报告(即使用 source-map-explorer ), 寻找大人物并仔细检查你的进口。
您朝着正确的方向迈出了一步。 Tree shaking 在文件边界上起作用,它会丢弃导入路径中未使用的文件并且没有副作用。
您的第一个示例无法使用 tree shaking,因为您的整个包都捆绑在一个文件中。
您的第二个示例捆绑到多个文件中,但您的主捆绑包(带有 index.js
输入)仍然是一个巨大的文件,它将所有内容捆绑在一起,而不是将 imports
单独留在其中。 (这只是基于您发布的构建过程的假设,请检查您的 index.js
包以验证这一点)。
您必须:
定义index.jsas externals的依赖。尝试将整个
/src
目录添加为外部目录。或
在构建过程中使用 babel 而不是 rollup 以保持导出和导入的完整性。
如果您有这样的问题,只需检查 material UI build process 和最终构建文件夹(npm i @material-ui/core
并查看 node_modules)。这是解决方案的一个很好的灵感。