We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
原文地址
最近在团队分享一些 webpack 技巧,于是便准备梳理出一篇博文总结一下,不过由于讲的内容太多了,无法描述的很详细,更多地是提供一个思路,希望读者感兴趣可以动手去实践实践。
配置多份 webpack 配置,通过 webpack-merge 进行合并,
├── common.js ├── dll.config.js ├── webpack.base.config.js ├── webpack.dev.config.js ├── webpack.prod.config.js // etc 同构配置,node middleware 等等
然后通过 npm scripts 执行 webpack 命令:
{ "scripts": { "dev": "webpack-dev-server --config ./webpack.dev.config.js", "build": "webpack --config ./webpack.prod.config.js", "start": "npm run dev", "pre": "webpack --config ./dll.config.js" }, }
node_modules/.bin/webpack-dev-server
node_modules/.bin/
想开启热更新,首先需要在入口文件进行配置:
// 入口文件 if(module.hot) { module.hot.accept(['./App'], () => { render(<App />, document.getElementById('app')) }) }
模块热更新机制:
最简单的方式:
直接在命令里面加上 webpack-dev-server --hot 即可开启热更新。
webpack-dev-server --hot
该参数相当于是做了:
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); module.exports = { plugins: [ new HotModuleReplacementPlugin(), ], devServer:{ hot: true, } };
当然如果你想要更加定制化的控制,你需要在 webpack 配置进行额外配置:
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); module.exports = webpackMerge(baseConfig, { plugins: [ new HotModuleReplacementPlugin(), ], devServer: { // 每次构建时候自动打开浏览器并访问网址 open: true, // 开启热更新 hot: true, // 设置静态资源地址如:/public,从这获取你想要的一些外链资源,图片。 contentBase: DIST_PATH, // 设置端口号 port: 9000, // 将热更新代码注入到模块中 inline: true, // 如果你希望服务器外部可访问 host: "0.0.0.0", // 设置 proxy 代理 proxy: { context: ['/api'], target: "//www.proxy.com", pathRewrite: {"^/api" : ""} }, // 设置 https https: true } });
关于 webpack 热更新原理我就不说了,感兴趣可以看下面两篇文章:
module.exports = { devtool: 'source-map', }
方便调试源代码
在大型应用减少每次构建的时间十分重要,动不动几十秒的编译时间令人发指,我在经过一些实践,总结下面一些方式,至少可以让你的编译速度快 1-2 倍。
首先第一点:缩小 Babel 的编译范围,并使用 webpack cache 缓存模块。
module.exports = { // 减小模块的查找范围 resolve: { modules: [path.resolve(__dirname, 'node_modules')], }, module: { rules: [ { test: /\.js?$/, use: [{ loader: 'babel-loader', query: { // 将 babel 编译过的模块缓存在 webpack_cache 目录下,下次优先复用 cacheDirectory: './webpack_cache/', }, }], // 减少 babel 编译范围,忘记设置会让 webpack 编译慢上好几倍 include: path.resolve(__dirname, 'src'), }, ] }, }
通过这步可以快上好几秒,另外你可以使用 DLLPlugin 预先打包好第三方库,避免每次都要去编译。开启 DLLPlugin 需要你额外配置一份 webpack 配置。
// dll.config.js const webpack = require('webpack'); const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin') const vendors = [ 'react', 'react-dom', 'react-router', 'redux', 'react-redux', 'jquery', 'antd', 'lodash', ] module.exports = { entry: { 'dll': vendors, }, output: { filename: '[name].js', path: path.resolve(__dirname, 'public'), library: '__[name]__lib', }, plugins: [ new DllPlugin({ name: '__[name]__lib', path: path.join(__dirname, 'build', '[name].manifest.json'), }), ] }
运行则会在 public 目录下得到 dll.js 和 dll.manifest.json 文件,然后需要在开发配置文件中关联。
dll.js
dll.manifest.json
const webpack = require('webpack'); module.exports = webpackMerge(baseConfig, { plugins: [ new DllReferencePlugin({ manifest: require('./public/dll.manifest.json'), }), ] });
另外需要在你的 html 模板里面引入 dll.js,webpack 不会自动帮你引入,用好这一步编译速度应该能快一倍左右的时间。
第三点就是使用 happypack 开启多核构建,webpack 之所以慢,是因为由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,webpack 构建慢的问题会显得严重。 也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。
在整个 webpack 构建流程中,最耗时的流程可能就是 loader 对文件的转换操作了,因为要转换的文件数据巨多,而且这些转换操作都只能一个个挨着处理。 Happypack 的核心原理就是把这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。
需要配置哪些 loader 使用 Happypack 就要改写那些配置,比如你想要修改 babel 为多核编译:
module.exports = { module: { rules: [ { test: /\.js?$/, use: ['happypack/loader?id=babel'], include: path.resolve(__dirname, 'src'), }, ] }, plugins: [ new HappyPack({ id: 'babel', loaders: [{ loader: 'babel-loader', query: { cacheDirectory: './webpack_cache/', }, }], }) ], };
设置 id=babel,webpack 会去找 plugins 中的 id 为 babel 的插件进行处理。配置其它的 loader 的方式也是类似,不过需要注意的是有的 loader 不支持多核编译。通过这一步应该至少能让你的编译速度快 1/3。
id=babel
最后一点是不要使用 webpack 里 css 模块化方案,我这里指的模块化指的是 css-loader 提供的模块化方式,我们先来看下它是怎么做的,首先它需要在你的 loader 中进行额外配置。
module.exports = webpackMerge(baseConfig, { module: { rules: [ { test: /\.css/, use: [ 'style-loader', { loader: 'css-loader', options: { // 开启 css 模块 modules: true, // 设置命名格式 localIdentName: '[name]__[hash:base64:5]' } } ] }, ] }, }
如果通过这种 css 模块化的方式,意味着你在写 React 组件的时候,需要这样去设置:
import styles from './index.css'; class Index extends React.Component { render() { return ( <div className={ styles.recursive }> xxxx <h1 className={ styles.header }></h1> </div> ); } } export default Index;
它相当于是在输出 css 文件的时候做了一层原名称到新名称的一次转化来保证 css 模块化的特性,输出的值就像这样:
Object { recursive: 'recursive__abc53xxxx', xxxxx: 'xxxxx__def884xxx', }
这样做不好的点在哪:
所以如果想要使用 css 模块化的可以尽量选择其它方案,比如 styledComponents 或者自己添加命名空间等等。
在发布上线的时候就需要考虑到很多性能优化的因素,比如如何有效地去利用浏览器的缓存,如何减少打包文件的体积等等这些因素都值得去优化。
关于如何高效地利用浏览器缓存,之前写过一篇文章详细描述了 webpack 持久化缓存实践,感兴趣可以看看。
我这里做个总结,我认为 webpack 在浏览器缓存需要做到以下几点:
那么我们要怎样通过 webpack 来完成上面的步骤呢?
首先不建议线上发布直接全部使用 DLLPlugin 插件来开启浏览器缓存,DLLPlugin 本身有几个缺点:
我认为的正确的姿势是:
具体如何拆分模块,我在 webpack 持久化缓存实践 已经说明,这里不再赘述。
想要减少打包后的体积,就需要使用到 webpack2 提供的 tree shaking 功能和 webpack3 提供的 scope hoisting 功能。
想要 tree shaking 生效,下面四点值得注意:
首先,模块引入要基于 ES6 模块机制,不再使用 commonjs 规范,因为 es6 模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,然后清除没用的代码。而 commonjs 的依赖关系是要到运行时候才能确定下来的。
另外对于引用第三方模块使用 tree shaking 功能,可以设置 mainFields 用于配置采用哪个字段作为模块的入口描述。 为了让 tree shaking 对 es6 生效,需要配置 webpack 的文件寻找规则为如下:
mainFields
module.exports = { resolve: { // 针对 npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件 mainFields: ['jsnext:main', 'browser', 'main'] }, };
对于一些死代码,就像下面这样:其大致原理是借助环境变量去判断执行哪个分支。
if (process.env.NODE_ENV === 'production') { console.log('你正在线上环境'); } else { console.log('你正在使用开发环境'); }
通过 shell 脚本的方式去定义的环境变量,例如 NODE_ENV=production webpack,webpack 是不认识的,对 webpack 需要处理的代码中的环境区分语句是没有作用的。
在构建线上环境代码时,需要给当前运行环境设置环境变量 NODE_ENV = 'production',webpack 相关配置如下:
const DefinePlugin = require('webpack/lib/DefinePlugin'); module.exports = { plugins: [ new DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), ], };
这样设置后 tree shaking 能有效清除跟生产环境无关的代码。
最后需要强调,webpack 只是指出了哪些函数用上了哪些没用上,要剔除用不上的代码还得经过 UglifyJS 去处理一遍。 需要开启代码压缩, tree shaking 才能真正将无用的代码消除。
如果想要开启 Scope hoisting,需要在额外配置 ModuleConcatenationPlugin 插件,并且 Scope hoisting 对下面的情况不生效:
这些我在 深入理解 webpack 文件打包机制 都有详细阐述,这里不多说了。
压缩代码可以使用 UglifyJsPlugin 这个插件对代码进行压缩,不过用过 UglifyJsPlugin 的你一定会发现在构建用于开发环境的代码时很快就能完成,但在构建用于线上的代码时构建一直卡在一个时间点迟迟没有反应,其实卡住的这个时候就是在进行代码压缩。
由于压缩 JavaScript 代码需要先把代码解析成用 Object 抽象表示的 AST 语法树,再去应用各种规则分析和处理 AST,导致这个过程计算量巨大,耗时非常多。
遇到这种情况可以改用 ParallelUglifyPlugin 插件,当 webpack 有多个 JS 文件需要输出和压缩时,原本会使用 UglifyJsPlugin 去一个个挨着压缩再输出, 但是 ParallelUglifyPlugin 则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJsPlugin 去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工作。
压缩 CSS 代码的话,因为使用到 extract-text-webpack-plugin 插件将代码从 js 中分离出来,可以通过 optimize-css-assets-webpack-plugin 插件进行压缩,详细的配置项可以看:optimize-css-assets-webpack-plugin
上面讲了很多优化方式,但是无法面面俱到,对此你需要对打包输出结果做分析,以确认下一步的优化思路。这里推荐两个打包分析工具:
简单地介绍下第二种方式,接入方式很简单:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }
当你启动 webpack 时候,会唤起浏览器弹出 treemap,通过分析图可以清楚地看到:
本文更多地介绍一些思路,详细的优化步骤可以自己去尝试,你肯定会有更多的收获的!
参考链接:
The text was updated successfully, but these errors were encountered:
大神,写的太好了,手动点赞
Sorry, something went wrong.
No branches or pull requests
原文地址
webpack 大型应用优化实践
前言
最近在团队分享一些 webpack 技巧,于是便准备梳理出一篇博文总结一下,不过由于讲的内容太多了,无法描述的很详细,更多地是提供一个思路,希望读者感兴趣可以动手去实践实践。
我认为理想的 webpack 配置:
配置多份 webpack 配置,通过 webpack-merge 进行合并,
然后通过 npm scripts 执行 webpack 命令:
node_modules/.bin/webpack-dev-server
,npm scripts 会自动把node_modules/.bin/
下的指令添加到环境中。区分好开发环境和生产环境:
开发过程:
优化开发体验
自动刷新 -> 模块热更新:
想开启热更新,首先需要在入口文件进行配置:
模块热更新机制:
最简单的方式:
直接在命令里面加上
webpack-dev-server --hot
即可开启热更新。该参数相当于是做了:
当然如果你想要更加定制化的控制,你需要在 webpack 配置进行额外配置:
关于 webpack 热更新原理我就不说了,感兴趣可以看下面两篇文章:
配置 sourcemap
方便调试源代码
减少构建时间
在大型应用减少每次构建的时间十分重要,动不动几十秒的编译时间令人发指,我在经过一些实践,总结下面一些方式,至少可以让你的编译速度快 1-2 倍。
首先第一点:缩小 Babel 的编译范围,并使用 webpack cache 缓存模块。
通过这步可以快上好几秒,另外你可以使用 DLLPlugin 预先打包好第三方库,避免每次都要去编译。开启 DLLPlugin 需要你额外配置一份 webpack 配置。
运行则会在 public 目录下得到
dll.js
和dll.manifest.json
文件,然后需要在开发配置文件中关联。另外需要在你的 html 模板里面引入 dll.js,webpack 不会自动帮你引入,用好这一步编译速度应该能快一倍左右的时间。
第三点就是使用 happypack 开启多核构建,webpack 之所以慢,是因为由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,webpack 构建慢的问题会显得严重。 也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。
在整个 webpack 构建流程中,最耗时的流程可能就是 loader 对文件的转换操作了,因为要转换的文件数据巨多,而且这些转换操作都只能一个个挨着处理。 Happypack 的核心原理就是把这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。
需要配置哪些 loader 使用 Happypack 就要改写那些配置,比如你想要修改 babel 为多核编译:
设置
id=babel
,webpack 会去找 plugins 中的 id 为 babel 的插件进行处理。配置其它的 loader 的方式也是类似,不过需要注意的是有的 loader 不支持多核编译。通过这一步应该至少能让你的编译速度快 1/3。最后一点是不要使用 webpack 里 css 模块化方案,我这里指的模块化指的是 css-loader 提供的模块化方式,我们先来看下它是怎么做的,首先它需要在你的 loader 中进行额外配置。
如果通过这种 css 模块化的方式,意味着你在写 React 组件的时候,需要这样去设置:
它相当于是在输出 css 文件的时候做了一层原名称到新名称的一次转化来保证 css 模块化的特性,输出的值就像这样:
这样做不好的点在哪:
所以如果想要使用 css 模块化的可以尽量选择其它方案,比如 styledComponents 或者自己添加命名空间等等。
发布上线:
在发布上线的时候就需要考虑到很多性能优化的因素,比如如何有效地去利用浏览器的缓存,如何减少打包文件的体积等等这些因素都值得去优化。
高效利用浏览器缓存
关于如何高效地利用浏览器缓存,之前写过一篇文章详细描述了 webpack 持久化缓存实践,感兴趣可以看看。
我这里做个总结,我认为 webpack 在浏览器缓存需要做到以下几点:
那么我们要怎样通过 webpack 来完成上面的步骤呢?
首先不建议线上发布直接全部使用 DLLPlugin 插件来开启浏览器缓存,DLLPlugin 本身有几个缺点:
我认为的正确的姿势是:
具体如何拆分模块,我在 webpack 持久化缓存实践 已经说明,这里不再赘述。
减少打包文件的体积
想要减少打包后的体积,就需要使用到 webpack2 提供的 tree shaking 功能和 webpack3 提供的 scope hoisting 功能。
想要 tree shaking 生效,下面四点值得注意:
首先,模块引入要基于 ES6 模块机制,不再使用 commonjs 规范,因为 es6 模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,然后清除没用的代码。而 commonjs 的依赖关系是要到运行时候才能确定下来的。
另外对于引用第三方模块使用 tree shaking 功能,可以设置
mainFields
用于配置采用哪个字段作为模块的入口描述。 为了让 tree shaking 对 es6 生效,需要配置 webpack 的文件寻找规则为如下:对于一些死代码,就像下面这样:其大致原理是借助环境变量去判断执行哪个分支。
通过 shell 脚本的方式去定义的环境变量,例如 NODE_ENV=production webpack,webpack 是不认识的,对 webpack 需要处理的代码中的环境区分语句是没有作用的。
在构建线上环境代码时,需要给当前运行环境设置环境变量 NODE_ENV = 'production',webpack 相关配置如下:
这样设置后 tree shaking 能有效清除跟生产环境无关的代码。
最后需要强调,webpack 只是指出了哪些函数用上了哪些没用上,要剔除用不上的代码还得经过 UglifyJS 去处理一遍。 需要开启代码压缩, tree shaking 才能真正将无用的代码消除。
如果想要开启 Scope hoisting,需要在额外配置 ModuleConcatenationPlugin 插件,并且 Scope hoisting 对下面的情况不生效:
这些我在 深入理解 webpack 文件打包机制 都有详细阐述,这里不多说了。
压缩代码
压缩代码可以使用 UglifyJsPlugin 这个插件对代码进行压缩,不过用过 UglifyJsPlugin 的你一定会发现在构建用于开发环境的代码时很快就能完成,但在构建用于线上的代码时构建一直卡在一个时间点迟迟没有反应,其实卡住的这个时候就是在进行代码压缩。
由于压缩 JavaScript 代码需要先把代码解析成用 Object 抽象表示的 AST 语法树,再去应用各种规则分析和处理 AST,导致这个过程计算量巨大,耗时非常多。
遇到这种情况可以改用 ParallelUglifyPlugin 插件,当 webpack 有多个 JS 文件需要输出和压缩时,原本会使用 UglifyJsPlugin 去一个个挨着压缩再输出, 但是 ParallelUglifyPlugin 则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJsPlugin 去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工作。
压缩 CSS 代码的话,因为使用到 extract-text-webpack-plugin 插件将代码从 js 中分离出来,可以通过 optimize-css-assets-webpack-plugin 插件进行压缩,详细的配置项可以看:optimize-css-assets-webpack-plugin
输入分析:
上面讲了很多优化方式,但是无法面面俱到,对此你需要对打包输出结果做分析,以确认下一步的优化思路。这里推荐两个打包分析工具:
简单地介绍下第二种方式,接入方式很简单:
当你启动 webpack 时候,会唤起浏览器弹出 treemap,通过分析图可以清楚地看到:
结语
本文更多地介绍一些思路,详细的优化步骤可以自己去尝试,你肯定会有更多的收获的!
参考链接:
The text was updated successfully, but these errors were encountered: