webpack优化探索之路
2019-10-09 14:39 孤独大兔子 阅读(237) 评论(0) 收藏 举报一、webpack是什么
webPack可以看做是模块打包器(module bundler):它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。用官网的话说,当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

webpack所能打包的文件
二、为什么我们要用webpack
前端发展到今天,可以说经历了几个重要的时代,之前我们提倡要语义话,结构、表现、行为相互分离到后来要求书写更加简单所以jquery风靡了很多年,现在想想满篇的dom查找,维护起来真的是难受,在这期间前端一直在寻求突破,直到有两件事情的发生,一件是nodejs的诞生,他让js不止在浏览器上解析,还能在服务器上进行解析,从此前端有了很大的发展空间,原来js还能像后端语言一样操作数据库,操作文件,在此基础上如webpack、Parcel、yarn、npm、group等工具的产生了,第二件是reactjs等组件的诞生,他用虚拟dom来代替传统的节点选择,加快了渲染速度,视图与数据分离、解耦,以数据驱动视图,开启了前端只关心数据的时代,后来2015年ES6横空出世,2016年HTML5发布,加上css3的动画以及媒体查询等功能,前端简直蛟龙入海,以前刀耕火种的时代过去了。
现在的前端项目技术选型你可能会发现,React、Flux、Angular、Aurelia、Mocha、Jasmine、Babel、TypeScript、Flow、Vue...他们都在试图让前端开发变得简单化、模块化、工程化,而恰恰浏览器在解析原生javascript的时候所欠缺的就是没有办法做到模块化,很多很多年前seajs很火,他的开发者可能前端的小伙伴都听说过,阿里的大神玉伯,seajs就是解决了让javascript模块化变成可能,并且动态按需加载,我们如果加载很多js文件,一个文件要想给另外一个文件暴露出去一部分数据或者一个变量,那只能将它定义在全局的作用域下,没有其他的办法。
开发一个小项目并不难,但一个大型的项目往往要具有N个外部引用类库,数十个(甚至更多)js/html/css/图片 等资源组成,随着项目工程的复杂化,这种依赖链会越来越长,想要所有的库都受控制,所有的开发人员定义的变量都商量好并且不被全局污染,会发现项目会越来越难以维护,但node可以解决这个问题,因为在nodejs的眼里,每一个文件都是一个模块。
以下举一个用nodejs引用模块的例子:
var hello ="lzhe";
module.exports ={msg:hello} //文件a.js
var msg = require("./a.js"); //文件b.js
console.log(hello); //打印a.js里定义的全局变量
通过以上方式,实现模块之间的引用,首先暴露一个对象,再导出变量,如果通过webpack打包后( webpack b.js a.js --mode production ),会在目录生成dist文件夹,里面的main.js就是打包生成以后的文件,引用后你会发现我们定义的全局变量已经不存在了。

另外,由于Http 的特性所致,分散的,小的静态资源在加载的时候更慢,静态资源( html/js/css/图片等资源 )过多导致上线时的重复性工作量增大,请求数的增多是导致页面渲染慢的一个很重要的原因,然而当这类静态资源很少的时候,手动合并/压缩 并没有问题,反之这些资源呈指数上升的时候,手动方案显示不是一个好办法。自动化方案可能是当下最好的解决办法,webpack 本身只能处理原生的JavaScript模块,但是他内置的 loader 转换器可以将各种类型的资源转换成 javascript模块,这样,任何资源都可以成为 webpack 可以处理的模块。比如说Webpack 本身是处理不了css的,但是它有css-loader,将css转换成js可以处理的模块,另外还有很多有意思的功能,这里不一一介绍了,直接官网api,更加直观。
三、安装及使用
目前webpack最新的版本是4,41。我在这篇文章里写了简单的应用,有兴趣的客官可以前去逛逛 https://www.cnblogs.com/change-oneself/p/10843584.html
四、优化让你的项目跑起来更快
我们知道webpack.json或者是指定的打包执行的文件是需要自己去写配置文件的,如果我们制作一个简单页面,有一个入口文件,把a.js打包成b.js,那么我们只做简单的配置就可以,简单的四个核心对象。
- 入口(entry)
- 输出(output)
- loader
- 插件(plugins)
但如果你不了解更深入一点的话,webpack会把你的所有文件都打包成一个文件,那么随着项目的复杂度增加,所打包以后的文件也会越来越大,以至于一个js文件会到几十兆,要怎么配置或者说配置文件写好了以后我们需要如何优化呢,你可以试试从以下几个方面入手。
1、去除没用的插件,打包配置开发环境与生产环境相分离。
这里提到的是首先要把开发环境和生产环境分离。(这里我选择在package.json里安装webpack-merge,并在build文件夹中创建webpack.base.conf.js与webpack.prod.conf.js用来区分两套环境,在webpack.prod.conf.js中这样引用)
const baseWebpackConfig = require('./webpack.base.conf')
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules:[]
},
output:{
path: "",
filename: "",
chunkFilename: ""
},
plugins: []
})
module.exports = webpackConfig;
其次webpack强大的地方是你可以用各种各样有意思的插件,他们能帮助你完成很有事情,关于插件,你可以看这里 https://www.webpackjs.com/plugins/,开发环境与生产环境分离后,在生产环境有一些辅助性的插件我们就没有必要再增加打包的负担了,比如说我们常用到的模块热替换插件 HotModuleReplacementPlugin、webpack-hot-middleware等,这个比较容易看出来,如果不知道的话,复制去上面的网址搜一下,就知道是做什么用的插件了,除了压缩,删除重复的和未使用的CSS规则等等有用的,其余增强功能的,监测环境的可自行斟酌。
2、提取第三方库
其实这里有两种方式,一种是在打包的时候,设置配置让他们别都打在app.js里,因为会太大了,告诉webpack可以分开打包
// 把通过npm包引用的第三方类库从入口文件中提取出来 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated // 把webpack的module管理相关基础代码从vendor中提取到manifest new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), // This instance extracts shared chunks from code splitted chunks and bundles them // in a separate chunk, similar to the vendor chunk // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }),
另一种方式是如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),那么我们可以用externals来阻止他来打包。
用法首先在模板文件中以cdn方式引用,在配置文件中配置
module.exports = { ... output: { ... }, externals : { react: 'react', redux: 'redux' } ... }
这样的话在应用程序中依旧可以以import的方式(还支持其他方式)引用:
import React from 'react';
import { createStore, combineReducers, applyMiddleware } from 'redux';
当然还有第三种方法,用 DLL 的方式提取第三方库。
3、代码压缩
- 压缩JS:Webpack内置UglifyJS插件、ParallelUglifyPlugin
会分析JS代码语法树,理解代码的含义,从而做到去掉无效代码、去掉日志输入代码、缩短变量名等优化。
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//...
plugins: [
new UglifyJSPlugin({
compress: {
warnings: false, //删除无用代码时不输出警告
drop_console: true, //删除所有console语句,可以兼容IE
collapse_vars: true, //内嵌已定义但只使用一次的变量
reduce_vars: true, //提取使用多次但没定义的静态值到变量
},
output: {
beautify: false, //最紧凑的输出,不保留空格和制表符
comments: false, //删除所有注释
}
})
]
使用webpack --optimize-minimize 启动webpack,可以注入默认配置的UglifyJSPlugin
- 压缩ES6:第三方UglifyJS插件
随着越来越多的浏览器支持直接执行ES6代码,应尽可能的运行原生ES6,这样比起转换后的ES5代码,代码量更少,且ES6代码性能更好。直接运行ES6代码时,也需要代码压缩,第三方的uglify-webpack-plugin提供了压缩ES6代码的功能,但如果是在webpack4.x中production是自动压缩的,如果项目还停留在webpack3.x阶段,需要引用插件来自行压缩js代码,引用UglifyjsWebpackPlugin,他的相关配置 https://github.com/webpack-contrib/uglifyjs-webpack-plugin
npm i -D uglify-webpack-plugin@beta //要使用最新版本的插件 //webpack.config.json const UglifyESPlugin = require('uglify-webpack-plugin'); //... plugins:[ new UglifyESPlugin({ uglifyOptions: { //比UglifyJS多嵌套一层 compress: { warnings: false, drop_console: true, collapse_vars: true, reduce_vars: true }, output: { beautify: false, comments: false } } }) ]
另外要防止babel-loader转换ES6代码,要在.babelrc中去掉babel-preset-env,因为正是babel-preset-env负责把ES6转换为ES5。
- 压缩css资源需要安装插件 optimize-css-assets-webpack-plugin
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCSSPlugin({ cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } }),
4、按需加载,代码分割
其实一般我们在加载一个网页的时候会把其中的全部js 代码都加载下来。但其实很多我们并不会用到,对于 web app 来说,我们更想要的是只加载当前 UI 的代码,没有渲染的页面不加载。这不会减少打包所需要的时间,但会大大加快访问页面所需要的时间,如果你有很多页面应用的话。
webpack有三种形式可以分割打包的代码,其中第一种是多入口和多出口,这在我们的Vue或React日常项目中不会用到,除非你自己搭建脚手架,第二种上面已经说过了提取第三方库,第三种方式为:
Dynamic Imports:动态加载。通过模块的内联函数调用来分割,需要结合 vue-router。
Webpack 的动态分割主要方式是使用符合 ECMAScript 提案的 import() 语法。语法如下
import('path/to/module') -> Promise
传入模块的路径,import() 会返回一个Promise。这个模块就会被当作分割点。意味着这个模块和它的子模块都会被分割成一个单独的 chunk。首先需要 webpack 配置文件的 output 字段,需要添加一个 chunkFileName 属性。它决定非入口 chunk 的名称。

这个动态代码分割功能是我们实现按需加载的前提。在 vue 的项目里,我们最终想要达到这样一个效果:
- 把每个路由所包含的组件,都分割成一个单独的 bundle
- 当路由被访问的时候才加载该路由对应的 bundle
接下来我们在配置路由的时候vue-router中,我们需要改成这种形式:

然后我们进行build,生成的文件发生了变化,而不是单纯的值生成了一个很大的app.js。

我们在进入页面调试页面中可以看到,当切换到另一个路由时,发现只会加载该页面对应的js了。
5、production关掉SourceMap
如果你在打包后产生后缀名为.map的文件,那是由于配置了sourcemap选项生成的,我们打包后代码经过压缩,如果产生问题,很难找到问题所在,不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。但也会导致打包时间变长,其实我觉得生产环境一般都是经过测试后才发版,而且我们前面讲过按需加载,完全不必要担心找不到问题所在。
- sourceMap本质上是一个映射关系,它知道dist目录下生成的js,对应src目录下相应js的行数。
- sourceMap文件中是VLQ编码集合,把打包生成的代码和源代码做一个映射。
- webpack.config.js中开启source-map,这样就会打包出.map文件。
直接修改配置 productionSourceMap: false 就可以了。
6、配置loader时,通过test、exclude、include缩小搜索范围
我们都知道在webpack在导入文件后,根据文件的后缀,去使用配合中loader去处理文件,比如使用es6开发的javascript文件需要使用babel-loader去处理,但由于 Loader 对文件的转换操作很耗时,需要让尽可能少的文件被 Loader 处理。为了尽可能少的让文件被 Loader 处理,可以通过 include 去命中只有哪些文件需要被处理,缩小命中范围。

这样选中范围,在解析的时候减少处理的范围,加快打包速度。
浙公网安备 33010602011771号