1. 9.0版本
1.1 动态路由修改
- 打包分析
Show chunks: All (3.3 MB)
js/app.f6e590defd09f0af0533.js (1.23 MB)
js/0.dcb7a6fef441f8b29aad.js (759.71 KB)
js/1.12b87a568748b35be091.js (753 KB)
js/vendor.3a71817ce04ee0d52abd.js (477.57 KB)
js/2.b41119393c5fed1db5a7.js (50.59 KB)
js/3.1e900085df4e92b118d0.js (44.05 KB)
js/4.cd0ae5676c3c19029aaa.js (30.54 KB)
js/5.43b81f7e8267bd84e89e.js (2.27 KB)
js/6.8d54cf0b69937a8c4ca4.js (1.73 KB)
js/manifest.aab2c6e6e57bd770452c.js (1.58 KB)

(1) 动态路由生成js文件没有自定义名
(2) 0.js和1.js代码几乎都是重复的。
(3) 2.js、3.js、4.js动态路由生成的代码体积太小。
- 设置打包配置
chunkFilename
webpack.config.production.js
output: {
path: config.path.dist,
- filename: 'js/[name].[chunkhash].js',
- chunkFilename: 'js/[id].[chunkhash].js',
+ filename: 'js/[name].[chunkhash:5].js',
+ chunkFilename: 'js/[name].[chunkhash:5].js',
publicPath: '?op=resource&encode=utf8&resource=/com/fr/wei/plugin/h5reportnew/dist/'
},
- 自定义
chunkName并进行路由合并
routerConfig.web.js
-import Login from 'bundle-loader?lazy!../../platform/view/Login/'
-import Directory from 'bundle-loader?lazy!../../platform/view/Directory'
-import ReportPage from 'bundle-loader?lazy!../../fr/page/ReportPage'
-import FormPage from 'bundle-loader?lazy!../../fr/page/FormPage'
-import WebPage from 'bundle-loader?lazy!../../platform/view/WebPage'
-import ChangePassword from 'bundle-loader?lazy!../../platform/view/ChangePassword/ChangePassword'
-import CprPage from 'bundle-loader?lazy!../../fr/page/CprPage'
+import Login from 'bundle-loader?lazy&name=platform!../../platform/view/Login/'
+import Directory from 'bundle-loader?lazy&name=platform!../../platform/view/Directory'
+import ReportPage from 'bundle-loader?lazy&name=FRPage!../../fr/page/ReportPage'
+import FormPage from 'bundle-loader?lazy&name=FRPage!../../fr/page/FormPage'
+import WebPage from 'bundle-loader?lazy&name=platform!../../platform/view/WebPage'
+import ChangePassword from 'bundle-loader?lazy&name=platform!../../platform/view/ChangePassword/ChangePassword'
+import CprPage from 'bundle-loader?lazy&name=platform!../../fr/page/CprPage'
- 打包分析
Show chunks: All (2.57 MB)
js/app.3027a.js (1.23 MB)
js/FRPage.792bb.js (771.79 KB)
js/vendor.6f983.js (477.57 KB)
js/platform.aee33.js (128.05 KB)
js/manifest.0559a.js (1.46 KB)

1.2 图表资源提前加载
- 图表资源加载分析
在ChartView组件的didMount中请求加载图表资源,导致图表空白1-2s才能渲染出来。 - 提前加载图表资源
<link rel="stylesheet" type="text/css" href="<%= htmlWebpackPlugin.options.sourcePath + '/com/fr/web/core/css/leaflet.css' %>"/>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.sourcePath + '/com/fr/mobile/js/appChart.js' %>"></script>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.sourcePath + '/com/fr/web/core/js/vancharts-all.js' %>"></script>
-
时间对比
测试数据.png
1.3 打包区分不同入口
- 多入口分析
项目支持多入口,不同入口打开同一个html页面。页面初始加载app.js、vendor.js、manifest.js资源。当路由跳转到platform或FRPage对应页面时,动态加载platform.js和FRPage.js。
当从模板页(cpt、frm)进入项目时,FRPage.js仍通过动态路由的方式加载,加载时机有些延迟,增加了页面打开时间(客户反映提升不大)。 - 打包生成多个
html页面,不同入口打开不同的html页面。当从登录页进入项目时,使用动态路由;当直接打开cpt或者frm模板时,不使用动态路由,提前加载对应js资源。 - 打包分析
Show chunks: All (4.55 MB)
js/sync.3bd37d4.js (1.98 MB)
js/app.cf1d197.js (1.23 MB)
js/FRPage.2285cd3.js (770.97 KB)
js/vendor.72c6bc3.js (478.69 KB)
js/platform.d5fb09a.js (128.02 KB)
js/manifest.43d0f75.js (1.46 KB)

1.4 代码分割
-
Performance分析图
有几个图表的表单.png - 生成的
app.js体积很大,但CommonsChunkPlugin只适用于多入口文件公共模块代码分割。本项目入口只有一个index.web.js,不符合CommonsChunkPlugin插件的应用场景。 -
webpack升级
(1)Cyclic dependency error
解决:https://github.com/marcelklehr/toposort/issues/20
(2) 其他见10.0 webpack升级遇到问题及解决方案。 - 代码分割
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 6,
automaticNameDelimiter: '-',
cacheGroups: {
//base代码分割
baseComponent: {
test: /[\\/]public[\\/]base[\\/]components/,
priority: 5,
name: 'baseComponent'
},
appBase: {
test: /[\\/]public[\\/]base/,
priority: 0,
name: 'appBase'
},
//react
react: {
test: /[\\/]node_modules[\\/](react\.*|redux\.*)|[\\/]lib[\\/]reactweb/,
priority: -5,
name: 'react',
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendor'
}
}
}
}
-
时间对比
测试数据
2. 10.0版本打包优化
2.1 知识扩展
2.2 问题分析
-
happypack报错
Cannot read property 'length' of undefined
if (resolve.length === 4) {
^
TypeError: Cannot read property 'length' of undefined
at resolveLoader (...\node_modules\happypack\lib\WebpackUtils.js:138:17)
at ...\node_modules\happypack\lib\WebpackUtils.js:126:7
at ...\node_modules\happypack\node_modules\async\lib\async.js:713:13
分析:happypack 4.X不兼容webpack 4.X。
解决:升级happypack为 5.X版本。
-
file-loader报错
Cannot read property 'fileLoader' of undefined
ERROR in ./node_modules/rs-styles/images/sampleStamp.png
Module build failed: TypeError: Cannot read property 'fileLoader' of undefined
at Object.module.exports (/Users/uri/Documents/connect/dashboard/node_modules/file-loader/index.js:14:28)
at Object.module.exports (/Users/uri/Documents/connect/dashboard/node_modules/url-loader/index.js:31:23)
分析:url-loader使用了旧版本的file-loader。
解决:更新升级file-loader版本。
参考:https://github.com/webpack/webpack/issues/6419
-
file-loader报错
file-loader fails with JSON files in Webpack 4
Module parse failed: Unexpected token m in JSON at position 0
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token m in JSON at position 0
at JSON.parse (<anonymous>)
at JsonParser.parse (/Users/jeremy/Documents/Development/webpack-file-loader-test/node_modules/webpack/lib/JsonParser.js:15:21)
分析:在webpack4.X版本中,默认支持json文件,无需loader处理。
解决:删除json处理的loader({test: /\.json$/,use: 'json-loader'})。
参考:https://github.com/webpack-contrib/file-loader/issues/259
4. 动态import()报错(☆☆)
ERROR in ./pages/Home/index.tsx 5:16
Module parse failed: Unexpected token (5:16)
You may need an appropriate loader to handle this file type.
| import { BeatLoader } from 'react-spinners';
| export const LoadableHomePage = Loadable({
> loader: () => import(
| /* webpackChunkName: "homepage" */
| './page'),
方法一:npm update acorn --depth 20、npm dedupe、删除node_modules、删除package.lock.json、npm install。
方法二:npm install webpack@4.28.4
参考:https://github.com/webpack/webpack/issues/8656
-
uglifyjs-webpack-plugin压缩动态import()语法报错
分析:uglifyjs-webpack-plugin只支持ES5代码压缩,不支持ES6代码压缩。
解决:使用terser-webpack-plugin替换掉uglifyjs-webpack-plugin。
参考:https://webpack.docschina.org/plugins/terser-webpack-plugin -
import()动态加载魔法注释webpackChunkName失效
分析:①在项目中使用了module:metro-react-native-babel-preset,该preset是babel为ReactNative应用提供的,ReactNative应用默认使用它转化代码。②该preset支持动态import(),无需再使用@babel/plugin-syntax-dynamic-import。③ 该preset会在babel打包过程中删除注释内容。
image.png
解决:babel配置中添加comments: true。
参考:https://github.com/webpack/webpack/issues/4861
https://babeljs.io/docs/en/options#comments
https://www.npmjs.com/package/metro-react-native-babel-preset - 打包生成的
bundle中含有动态import()之外的js文件
image.png
分析:optimization.splitChunks在进行代码分割时,会默认将不同chunk引入的相同modules进行分割,避免这些代码重复打包到不同的bundle。
解决:配置optimization.splitChunks中的cacheGroups,将共用代码提取到自定义的group中。
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
commons: {
minChunks: 2,
priority: -20,
name: 'commons',
}
}
}
}
-
webpack打包资源过大警告
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
js/commons.532a8.js (515 KiB)
js/vendor.b2172.js (414 KiB)
js/BIPage.dafcb.js (477 KiB)
js/app.c64e5.js (1.2 MiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
app (1.77 MiB)
js/react.97c29.js
js/vendor.b2172.js
js/app.c64e5.js
分析:webpack推荐打包生成的asset单个资源大小和入口资源大小在244 KiB以内。
隐藏:performance: { hints: false }
参考:https://github.com/webpack/webpack/issues/3486
-
Tree Shaking为何不生效?
分析:关于lodash,已经通过babel-plugin-lodash实现了Tree Shaking。对于业务模块,由于react-native使用的打包工具metro不支持Tree Shaking,因此不支持。
解决:如果babel配置文件中使用module:metro-react-native-babel-preset,则可以支持Tree Shaking。但这样会导致react-native项目无法打包。
参考:https://www.npmjs.com/package/metro-react-native-babel-preset
https://github.com/facebook/metro/issues/227
2.3 打包优化
2.3.1 代码分割
- 同步代码分割
通过optimization.splitChunks.cacheGroups,对base、reactweb、react以及node_modules分别进行同步代码分割,生成appBase.js、reactweb.js、react.js以及vendors.js。
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 5,
automaticNameDelimiter: '-',
cacheGroups: {
//同步模块代码分割
appBase: {
test: /[\\/]public[\\/]base/,
priority: 0,
name: 'appBase',
chunks: 'initial'
},
reactWeb: {
test: /[\\/]lib[\\/]reactweb/,
priority: 0,
name: 'reactWeb',
chunks: 'initial'
},
//node_modules代码分割
react: {
test: /[\\/]node_modules[\\/](react\.*|redux\.*)/,
priority: -5,
name: 'react',
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendor'
}
}
}
}
- 异步代码分割
通过动态import(),对Login、Directory、ReportPage、FormPage、BIPage、WebPage、ChangePassword进行异步代码分割。
Loadable({
loader: () => import(/*webpackChunkName: "Login"*/'../../platform/view/Login'),
loading: () => null,
})
- 打包

- 动态
import()异步代码分割生成的一些bundler体积非常小。可以利用魔法注释中的webpackChunkName合并bundler
2.3.2 Base Tree Shaking
一. 问题分析
-
base文件夹中绝大部分模块(modules)只在BIPage、ReportPage、FormPage等异步Chunks中使用。为什么没有被打包到这些异步模块中,而是全部被同步代码分割出来了呢?
即:为什么动态import()异步代码分割对base中模块modules失效?
如果没有对
base的同步代码分割,这些base模块会被打包到入口app.js。
- 分析
一个模块如果被动态import()对应的异步chunk引入,同时被同步chunk引入,则该module会被打包进入同步chunk对应的bundler。
二. 逐个切断依赖关系
- 使
app chunk依赖树中不引入base/index模块。
能够最大限度的降低app.js的bundler体积。但app中的依赖树比较复杂,逐个修改的工作量比较大。依赖树中只要有一条线指向base/index模块,就不能解决问题。 - 如何查找
app chunk依赖树中哪些modules引入了base/index.js?
(1) 注释掉那些动态import()引入的chunks,只打包app对应的chunk。
(2) 通过分析工具可以查看module之间的依赖关系。
(3) 找到依赖base/index.js的模块,修改依赖关系。
image.png

对
app bundler和platform bundler过滤base modules。在进入模板页之前,减少js bundler的体积。
三. babel插件
- 三个插件
(1) babel-plugin-module-resolver 插件
// Use this:
import MyUtilFn from 'utils/MyUtilFn';
// Instead of that:
import MyUtilFn from '../../../../utils/MyUtilFn';
可以实现webpack中的resolve.alias的功能。
参考:custom-aliases-in-react-native-with-babel。
(2) babel-plugin-import
import { TimePicker } from "antd"
↓ ↓ ↓ ↓ ↓ ↓
var _button = require('antd/lib/time-picker');
可以实现webpack中第三方库的Tree Shaking功能。
(3) babel-plugin-transform-imports
//Causes this code:
import { MyModule } from 'my-library';
import { App } from 'my-library/components';
import { Header, Footer } from 'my-library/components/App';
//to become:
import MyModule from 'my-library/MyModule';
import App from 'my-library/components/App';
import Header from 'my-library/components/App/Header';
import Footer from 'my-library/components/App/Footer';
可以实现第三方库与本地代码的Tree Shaking。
第一个插件的作用是实现
resolve.alias。后两个插件的作用是实现tree shaking。后两个插件和第一个插件共同使用时,后两个插件不生效。
- 最佳实践
由于babel-plugin-module-resolver与另外两个插件共用使用时,另外两个插件不生效。因此,H5端使用webpack自身支持的resolve.alias和另外两个插件的其中之一(推荐babel-plugin-transform-imports)实现Tree Shaking。App端使用babel-plugin-module-resolver插件实现alias。
image.png - 注意事项
(1) 使用babel-plugin-transform-imports转换插件实现TreeShaking,需要保证一个module对应一个文件。
module.exports = function(importName, matches) {
if(components.includes(importName)) {
return `base/components/${importName}`
}
if(transMap[importName]) {
return transMap[importName]
}
console.error(importName + ` must define the transform import format!`);
};
(2) 修改transformImport.js中转换函数后需要清除缓存文件

使用默认的缓存目录
node_modules/.cache/babel-loader,如果在任何根目录下都没有找到node_modules目录,将会降级回退到操作系统默认的临时文件目录。
2.3.3 CSS资源
- 项目中外部式样式和嵌入式样式比较少,对页面加载影响微乎其微。
- 因为与
app共用代码。项目中绝大部分样式是通过StyleSheet.create({})以行内形式嵌入,导致打包输出的js文件体积增加。但如果不修改写法,行内样式无法抽出。
2.3.4 图片资源
- 图片资源预想的是将小图片转化为
base64字符串或者使用CSS spirit。后来发现遇到问题如下:
image.png
(1) 由于现有icon.js的写法。使用base64字符串会将没有使用到的小图标也打包到js文件中。造成js中图标体积约400KB。
(2) CSS spirit图片拼接比较麻烦,现有的以webpack-spritesmith、postcss-sprites为代表的插件也是以css文件为基础,需要修改现有Icon组件的实现方式。即使解决了该问题,雪碧图拼接会将所有小图标拼成一个大图片,包括使用不到的图标。
3. 代码逻辑
- 区分不同入口逻辑
从js分包的角度来看,动态import()异步资源加载不适用于用户直接打开模板时的入口,js分包已经做了入口区分。从代码逻辑上来看,不同入口代码逻辑也应该做区分,减少直接打开模板生成的bundler体积。 - 异步阻塞逻辑优化
页面渲染之前的异步回调会推迟页面初始渲染时间。







