Skip to content
New issue

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 2/3 中一些常见的优化措施 #52

Open
dwqs opened this issue May 28, 2017 · 14 comments
Open

Webpack 2/3 中一些常见的优化措施 #52

dwqs opened this issue May 28, 2017 · 14 comments

Comments

@dwqs
Copy link
Owner

dwqs commented May 28, 2017

Webpack 是一款强大的前端构建工具, 社区对其介绍的相关文章已经很多了, 本文不再赘述. 基于 Webpack 2/3, 本文是对我在搭建团队前端脚手架的过程中, 搜罗的 Webpack 2/3 常见的优化措施的一个总结.

如果你还不了解 Webpack 2/3, 可以先看下 Webpack 2/3 快速入门

1. 分离第三方依赖

开发环境下, 通常会采取 HMR 模式来提高开发效率. 但一般情况下, 我们只会更改自身的业务文件, 不会去更改第三方的依赖, 但 webpack 在 rebuild 的时候, 依旧会 build 所有的依赖. 因而, 为减少 rebuild 的时间, 我们可以分离第三方依赖, 在项目启动之前, 将其单独打包和引入.

这要借助 DllPlugin 插件.

我们定义一份生成 dll 的配置文件:

## webpacl.dll.config.js
module.exports = {
    entry: {
        vendor: [
            'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill', '...'
        ]
    },
    output: {
        path: path.join(__dirname, './public/', 'dist'),
        filename: '[name].dll.js',
        library: '[name]_library'
    },
    plugins: [
        new webpack.DllPlugin({
          path: path.join(__dirname, './public/', 'dist', '[name]-manifest.json'),
          name: '[name]_library'
      })
    ]
}

生成 dll 文件之后, 可以根据环境变量在页面的静态文件中引入:

这样, 在每次 rebuild 的时候, webpack 都不会去重新 build vendor, 能极大减少 rebuild 的时间, 提升开发效率.

仅在开发环境下使用

2. 多进程构建

Webpack的构建过程是单进程的, 利用 HappyPack 可让 loader 对文件进行多进程处理, 其原理图如下:

在业务文件依赖越多和复杂的情况下, HappyPack 对 Webpack 构建效率的提升会越明显. 下图是我在项目使用 HappyPack 前后的一张构建时间对比图:

HappyPack 会充分利用系统的资源来提升 Webpack 的构建效率, 所以系统本身的硬件配置会对 HappyPack 的使用有一定的影响.

HappyPack 不限于处理 js 文件, 也可以同时处理 css/vue 等其它类型文件. HappyPack 支持多个实例, 可以创建多个实例来分别处理不同的类型文件:

let HappyPack = require('happypack');
let os = require('os');
let happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {

	...
	
	plugins: [
		new HappyPack({
			id: 'vue',
          threadPool: happyThreadPool,
          cache: true,
          verbose: true,
          loaders: ['vue-loader'],
		}),
		
		new HappyPack({
			id: 'js',
          threadPool: happyThreadPool,
          cache: true,
          verbose: true,
          loaders: ['babel-loader'],
		})
		
		# others
	]
	
	...

}

此外, HappyPack 同时还利用缓存来使得 rebuild 更快.

开发环境和生产环境下均可使用. 关于其原理分析, 请看 HappyPack 原理解析

3. 提取公共的依赖模块

无论是单页还是多页应用, 在生产环境下, 通常都会利用 CommonsChunkPlugin 插件来提供公共的依赖模块:

new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    minChunks: ({resource}) => {
    	resource && 
    	resource.indexOf('node_modules') &&
    	resource.match(/\.js$/)
    }
}),

上述的配置会提取 node_modules 下的所有模块, 打包出来的结果可能是这样的:

打包结果分析图由 webpack-bundle-analyzer 提供

这样提取了公共模块之后, 的确会减少业务包的大小, 但是, 这种方式会导致两个问题:

  • 业务越复杂, 三方依赖会越多, vendor 包会越大
  • 没有隔离业务路由组件, 所有的路由都有可能会去加载 vendor, 但并不是所有的路由组件都依赖 node_modules 下的所有模块

所以, 上述提取公共依赖的方式不可取. 我们应该去分析业务依赖和路由, 尽可能将所有路由组件的公共依赖提取出来:

entry: {
    app: path.resolve(__dirname, '../src/page/index.js'),
    vendor: [
        'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill',
        'axios', '....'
    ]
},

new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    filename: "vendor.js"
}),

前后两种方式打包出来的 vendor 大小对比:

既要去提取公共依赖, 也要避免 vendor 包过于太大.

4. 文件分离

文件分离主要是将图片和 CSS 从 js 中分离. 图片和 CSS 都是 Webpack 需要构建的资源, 通过某种配置, 图片可以以 base64 的方式混淆在 js 文件中, 这会增加最终的 bundle 文件的大小. 在 生产环境下, 应该将图片和 CSS 从 js 中分离:

  • 在生产环境下, 通过自定义插件, 将图片的本地引用替换为 CDN 的链接
  • 在生产环境下, 通过 ExtractTextPlugin 来 提取 CSS.

5. 资源混淆和压缩

Webpack提供的 UglifyJS 插件由于采用单线程压缩, 速度比较慢,
可以使用 Parallel 插件进行优化:

let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
let os = require('os');

new ParallelUglifyPlugin({
    workerCount: os.cpus().length,
    cacheDir: '.cache/',
    uglifyJS: {
        compress: {
            warnings: false,
            drop_debugger: true,
            drop_console: true
        },
        comments: false,
        sourceMap: true,
        mangle: true
    }
})

6. Gzip 压缩

生产环境下, 如果想进一步减小 bundle 文件的大小, 可以使用 Gzip 压缩.

let CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
    plugins: [
        new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.(js|html)$/,
            threshold: 10240,
            minRatio: 0.8
        })
    ]
}

Gzip 压缩能有效减少 bundle 的文件大小:

部署上线时, 服务端也需要开启 gzip 压缩

7. 按需加载

在单页应用中, 一个应用可能会对应很多路由, 每个路由都会对应一个组件; 如果将这些组件全部全部放进一个 bundle, 会导致最终的 bundle 文件比较大(看上图的 app bundle 文件). 因而, 我们需要利用 Webpack 的 Code Splitting 功能, 将代码进行分割, 实现路由的按需加载.

在 Vue 中, 利用 vue-router懒加载功能, 是比较容易实现按需加载的:

当访问首页时, 会去加载 Index 组件, 此时并不会加载 Info 组件; 只有当路由切换为 /info 时, Info 组件才会被加载.

以上是个人的一些总结, 如有不足请指正, 如有遗漏, 欢迎补充.

vue-startup 是基于上述的一些优化措施写的一个 Vue 的脚手架, 欢迎 star. 其 TypeScript 版本: vue-typescript

相关文章

@dwqs dwqs changed the title Webpack 2 常见的优化措施 Webpack 2 中一些常见的优化措施 May 28, 2017
@think2011
Copy link

赞,目前已知的优化方式都在这里了

@Maopy
Copy link

Maopy commented May 30, 2017

require.ensure 可以换成 import() 了吧

@zhoufeifan
Copy link

可以的,但是为什么在第三点的时候不让看app.js的大小?

@dwqs
Copy link
Owner Author

dwqs commented May 31, 2017

@Maopy 可以

@dwqs
Copy link
Owner Author

dwqs commented May 31, 2017

@zhoufeifan 故意隐藏的 哈哈

@fengmiaosen
Copy link

mark

@ghost
Copy link

ghost commented Jun 2, 2017

如何在开启 map 的时候 不gzip压缩map文件?
可以看看你的配置文件吗

@zwwtj2014
Copy link

提取公共模块后,webpack不会像require一样去自加载需要的模块,需要在html手动引这个vendor,有没有好的方法不让手动引?

@ghost
Copy link

ghost commented Jun 8, 2017

@zwwtj2014 可以使用 html-webpack-plugin 这个插件看看

@zwwtj2014
Copy link

@samchanye 之前我是考虑过这个插件的,但是我现在的场景有几个问题:

  1. 页面多,不太希望手动配n个,可能我就需要写个扫描函数去扫描所有的html页面
  2. 目前我只是想引个js,感觉为了引个js,写一大坨new HtmlWebpackPlugin() 太恶心了,而且感觉还不如直接在html手动引这个vendor

所以后面决定不使用这个插件。。。但是一直没想到有好的解决方法

@ghost
Copy link

ghost commented Jun 8, 2017

@zwwtj2014
Copy link

@samchanye 👍 非常感谢!

看起来好像满足我的需求,我来试试!

@dwqs dwqs changed the title Webpack 2 中一些常见的优化措施 Webpack 2/3 中一些常见的优化措施 Jul 22, 2017
@liyatang
Copy link

不太理解 “dll 仅在开发环境下使用”。 生成环境照常用,还可以减少生成打包速度

@njleonzhang
Copy link

大神真是高产

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants