前言
webpack打包大多数前端工程师们都已经用过,然后今天我想和大家分享的是webpack如何打包才能输出 最优生产环境文件 ,主要针对两种人群:未自己手把手配置过webpack的人、配置过webpack但是不熟悉或者不知所以然的的人。如果fe大神看到请勿略此文,谢谢!
准备工作
在做讲解之前,我希望大家先去我的github上clone下我的demo项目,然后按照我的讲解亲自code一边!
最基本的打包构建
这是项目目录结构:
ps:先来看下最简单的打包,这边为了模拟打包文件大点,index.js引入了一些用不到的模块,然后webpack只做了最简单的js压缩处理。
//index.js import React from 'react'; import { render } from 'react-dom'; import { Router, Route, IndexRoute, hashHistory } from 'react-router'; import Redux from 'redux'; import reactRedux from 'react-redux'; import App from './app/App'; import antd from 'antd'; import 'antd/dist/antd.min.css'; import './assets/common.scss'; import './index.scss'; render(<App/>, document.getElementById("app"));
//webpack .... plugins: [ new webpack.optimize.UglifyJsPlugin({ output: { comments: false }, compress: { warnings: false } })]
在webpack刚出来的时候,大多数人使用webpack其实和用grunt、gulp一样,把项目中的引用到的模块、样式文件等都打包成一个js文件。这样做的缺点: 项目越庞大,打包出来的js文件越大,打包时间越长,最关键的是在单页面应用当中,会很大程度加大首屏加载时间,用户体验不好
上图可以看出打包时间7s左右,一个app.js文件达到352kb。然后这边还不包括antd.min.css(大概400kb antd-ui框架样式),你要想这仅仅是我这边只加了react开发需要用的一些基本模块,业务逻辑和业务css样式基本没有的情况下的数据。实际项目这个数据肯定还要来的大得多。
开始优化
首先我们考虑的是单个文件过大,拆分成多个打包。
css与js分离
把一个超大文件,先按js和css拆分成两个文件,然后页面并行加载这两文件肯定比加载一个文件来的快的,然后文件体积大小肯定也是有所缩小的。
extract-text-webpack-plugin
//webpack ... plugins: [ new webpack.optimize.UglifyJsPlugin({ output: { comments: false }, compress: { warnings: false } }), new ExtractTextPlugin({ filename:'css/[name].css', allChunks: true }) ]
打包时间有所缩短,app.js的文件体积也有所缩小。由于我这边业务css和业务逻辑代码基本没有,所以这次优化效果不显著。但是及时这样,app.js还是要比我们心里预计的来的大的多。
公共模块与业务模块分开打包
在实际项目当中,我们引入的模块其实可以分为 公共模块 与 业务模块 。
webpack把入口分为两个,一个业务主入口,另一个公共模块打包入口
CommonsChunkPlugin
//webpack entry:{ vendors:['react', 'redux', 'react-dom', 'react-redux', 'react-router', 'antd/dist/antd.min.css',//ui框架样式也打包进来 PATHS.ASSETS.join('common.scss')], app:'./index.js' }, ... plugins: [ new webpack.optimize.UglifyJsPlugin({ output: { comments: false }, compress: { warnings: false } }), new ExtractTextPlugin({ filename:'css/[name].css', allChunks: true }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendors', filename: 'js/[name].js', warn:false }), ]
时间为什么长了呢?其实这个不难理解,因为ui框架(antd)样式也打包进来了。打包成的四个文件:app.js/app.css、vendors.js/vendors.css。app是你业务逻辑代码和业务样式,vendors是你公共引用模块逻辑代码和公共样式。在实际大项目中,这四个文件js部分和css部分一般大小都差不多,所以分成四个文件后并行加载能大大缩减首页文件加载时间!ps:这边由于业务代码基本没有 所以app和vendors文件大小差异过大。
利用插件进一步优化打包文件
通过上面js和css分离,模块划分分离两大步骤,在单页面开发当中基本上你的文件划分定了。在这样的情况下,如果还想减少加载时间,提高体验。在自动化工程这块我们只能在文件体积上做文章了,继续减少文件体积。
optimize-css-assets-webpack-plugin与ModuleConcatenationPlugin
//css压缩 new OptimizeCssAssetsPlugin({ // assetNameRegExp: /.css$/g, // cssProcessor: require('cssnano'), cssProcessorOptions: { discardComments: { removeAll: true } }, canPrint: true }), //webpack3.0以上 new webpack.optimize.ModuleConcatenationPlugin(),
由于进一步压缩css和js导致时间打包会有所延长,但是效果还是有的上图可以看出js和css都一定程度缩小了。
衍生问题-打包时间过长怎么办
每次修改业务代码打包都会重新打包公共模块,但是实际情况公共模块打包基本是不会去修改的,那么我么你如果把公共模块打包单独提出来,每次只打包业务模块,这样打包时间是不是会大大缩减?事实上,webpack确实提供了这样的功能-DllPlugin与DllReferencePlugin
DllPlugin与DllReferencePlugin
Dll这个概念应该是借鉴了Windows系统的dll。一个dll包,就是一个纯纯的依赖库,它本身不能运行,是用来给你的app引用的。
打包dll的时候,Webpack会将所有包含的库做一个索引,写在一个manifest文件中,而引用dll的代码(dll user)在打包的时候,只需要读取这个manifest文件,就可以了。
这么一来有几个好处:
- Dll打包以后是独立存在的,只要其包含的库没有增减、升级,hash也不会变化,因此线上的dll代码不需要随着版本发布频繁更新。
- App部分代码修改后,只需要编译app部分的代码,dll部分,只要包含的库没有增减、升级,就不需要重新打包。这样也大大提高了每次编译的速度。
假设你有多个项目,使用了相同的一些依赖库,它们就可以共用一个dll。
如何使用呢?
首先要先建立一个dll的配置文件,entry只包含第三方库:
//webpack-dll /*webpack-dll页面配置*/ const path = require('path'); const webpack = require('webpack'); //把css样式从打包文件里面分离出来 const ExtractTextPlugin = require('extract-text-webpack-plugin'); //css压缩 const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const PATHS = require('./PATHS'); let dllConfig = { entry:{ vendors:['react', 'redux', 'react-dom', 'react-redux', 'react-router', 'antd/dist/antd.min.css', PATHS.ASSETS.join('common.scss')] }, module:{ rules: [{ test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: 'css-loader?sourceMap' }) }, { test: /\.scss$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader?sourceMap', 'sass-loader?sourceMap'] }) }] }, output:{ path:PATHS.DIST,//打包编译完的文件根目录 filename: "js/[name].js",//打包编译完文件路径和名称 library: '[name]', }, plugins: [ new ExtractTextPlugin({ filename:'css/[name]-[contenthash:8]-dll.css', allChunks: true }), new OptimizeCssAssetsPlugin({ // assetNameRegExp: /.css$/g, // cssProcessor: require('cssnano'), cssProcessorOptions: { discardComments: { removeAll: true } }, canPrint: true }), new webpack.optimize.UglifyJsPlugin({ output: { comments: false }, compress: { warnings: false } }), //webpack3.0以上 new webpack.optimize.ModuleConcatenationPlugin(), new webpack.DllPlugin({ path: 'manifest.json', name: '[name]', context: __dirname, }), ] }; module.exports = dllConfig;
webpack.DllPlugin的选项中,path是manifest文件的输出路径;name是dll暴露的对象名,要跟output.library保持一致;context是解析包路径的上下文,这个要跟接下来配置的dll user一致。
运行Webpack,会输出两个文件一个是打包好的vendor.js,一个就是manifest.json,长这样:
{ "name": "vendors", "content": { "./node_modules/process/browser.js": { "id": 0, "meta": {} }, "./node_modules/react/index.js": { "id": 1, "meta": {} }, "./node_modules/warning/browser.js": { "id": 2, "meta": {} }, "./node_modules/prop-types/index.js": { "id": 3, "meta": {} }, "./node_modules/invariant/browser.js": { "id": 4, "meta": {} }, "./node_modules/fbjs/lib/emptyFunction.js": { "id": 5, "meta": {} }, "./node_modules/object-assign/index.js": { "id": 6, "meta": {} }, .......
Webpack将每个库都进行了编号索引,之后的dll user可以读取这个文件,直接用id来引用。
Dll user的配置:
const webpack = require('webpack'); module.exports = { output: { path: 'build', filename: '[name].[chunkhash].js', }, entry: { app: './src/index.js', }, plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./manifest.json'), }), ], };
运行Webpack之后,结果如下:
明显速度快了,文件也小了。
平时开发的时候,修改代码后重新编译的速度会大大减少,节省时间。
结尾
其实还有一些优化,高版本的webpack比低版本的webpack打包要快而且文件要小,这属于webpack本身的性能优化带给我们的,例如:
- Scope Hoisting-作用域提升 加快减少闭包函数数量从而加快js执行速度
- 本身打包速度提升 可以自己升级webpack去体验下。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。