新建项目
目录结构
1 | . |
安装webpack和webpack-cli
1 | npm install webpack webpack-cli -D |
编辑package.json文件
1 | ... |
Babel
Babel 是一个让我们能够使用 ES 新特性的 JS 编译工具,我们可以在 webpack
中配置 Babel
,以便使用 ES6、ES7 标准来编写 JS 代码。
babel-loader 和 @babel/core
- babel-loader: 转义 js 文件代码的 loader
- @babel/core:babel 核心库
安装
1 | npm install babel-loader @babel/core -D |
编辑webpack.base.js
1 | const { |
引入了一个utils.js工具js
1 | const path = require('path'); |
编辑index.js
1 | const func = () => { |
构建
1 | npm run start |
关于mode,它有三个参数production
,development
,none
,前两个是有预设的插件,而最后一个则是什么都没有,也就是说设置为none
的话,webpack就是最初的样子,无任何预设,需要从无到有开始配置。
- development:是告诉程序,我现在是开发状态,也就是打包出来的内容要对开发友好。在此mode下,就做了以下插件的事,其他都没做,所以这些插件可以省略。
1 | module.exports = { |
NamedModulesPlugin:没有NamedModulesPlugin
,模块就是一个数组,引用也是按照在数组中的顺序引用,新增减模块都会导致序号的变化。有了NamedModulesPlugin
,模块都拥有了姓名,而且都是独一无二的key,不管新增减多少模块,模块的key都是固定的。
development:
1 | (function(modules) { |
production:
1 | ! function (e) { |
NamedChunksPlugin:还有一个NamedChunksPlugin
,这个是给配置的每个chunks命名,原本的chunks也是数组,没有姓名。
- 在正式版本中,所省略的插件们,如下所示
1 | module.exports = { |
UglifyJsPlugin:混淆&压缩JS
ModuleConcatenationPlugin:此插件仅适用于由 webpack 直接处理的 ES6 模块。在使用转译器(transpiler)时,你需要禁用对模块的处理(例如 Babel 中的 modules 选项)。
NoEmitOnErrorsPlugin:这个就是用于防止程序报错,就算有错误也给我继续编译,很暴力的做法呢。
构建之后的文件
1 | (function (modules) { |
实现它的模块化的地方不看,主要看编译出来的文件,es6语法没有被转成es5语法
@babel-preset-env
如果要转义箭头函数,需要使用到 @babel/plugin-transform-arrow-functions
这个插件 同理转义 class
需要使用 @babel/plugin-transform-classes
,但是如果还需要转义其他的(比如:async await),一个一个添加不合理,所以@babel-preset-env
整合了它们
安装
1 | npm install @babel/preset-env -D |
编辑.babelrc文件
1 | { |
这时候打包的就有转义语法了
1 | (function(modules) { |
但是这样也只是转换了语法,而没有转换API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,这样就导致了一些新的 API 老版浏览器不兼容。
@babel/polyfill
使用它来兼容,这边只说按需编译。
安装
1 | npm install @babel/polyfill -D |
编辑.babelrc文件
1 | { |
编辑index.js文件
1 | import '@babel/polyfill' // 引入 |
构建
1 | npm start |
编译出来的文件大了挺多的
@babel/runtime 和 @babel/plugin-transform-runtime
babel-polyfill
会污染全局作用域, 如引入 Array.prototype.includes
修改了 Array 的原型,除此外还有 String等。也会引入新的对象: Promise
、WeakMap
等。
@babel/runtime
的作用:
提取辅助函数。ES6 转码时,babel 会需要一些辅助函数,例如 _extend。babel 默认会将这些辅助函数内联到每一个 js 文件里, babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块
babel-runtime
中,这样做能减小项目文件的大小。提供
polyfill
:不会污染全局作用域,但是不支持实例方法如 Array.includes
@transform-runtime
的作用:
babel-runtime
更像是分散的polyfill
模块,需要在各自的模块里单独引入,借助 transform-runtime 插件来自动化处理这一切,也就是说你不要在文件开头 import 相关的polyfill
,你只需使用,transform-runtime
会帮你引入
安装
1 | npm install @babel/runtime-corejs2 @babel/plugin-transform-runtime -D |
编辑.babelrc文件
1 | { |
编辑index.js文件
1 | const func = () => { |
构建
1 | npm start |
@babel/plugin-proposal-decorators
添加装饰器支持
安装
1 | npm install @babel/plugin-proposal-decorators -D |
编辑.babelrc文件
1 | { |
因为我没使用到,所以忽略装饰器支持。
引入tree shaking
它可以剔除JavaScript中用不上的死代码。它依赖静态的ES6模块化语法,需要配置Babel让它保留ES6模块化代码语句交给webpack。
编辑.babelrc.json文件
1 | { |
测试
1 | //index.js |
构建
需要Uglifyjs之类的配合(webpack4,设置mode为production就可以)。可以在构建的shell后面添加--display-used-exports
参数
Webpack
几种 hash 的区别
hash
hash
和每次build
有关,没有任何改变的情况下,每次编译出来的hash
都是一样的,但当你改变了任何一点东西,它的hash
就会发生改变。简单理解,你改了任何东西,
hash
就会和上次不一样了。
chunkhash
chunkhash
是根据具体每一个模块文件自己的的内容包括它的依赖计算所得的hash
,所以某个文件的改动只会影响它本身的hash
,不会影响其它文件(但是会影响引入的css文件)。简单理解,改变了js文件不会影响其他js文件的chunkhash,但会影响到被引入的css的chunkhash
contenthash
它的出现主要是为了解决,让
css
文件不受js
文件的影响。比如foo.css
被foo.js
引用了,所以它们共用相同的chunkhash
值。但这样子是有问题的,如果foo.js
修改了代码,css
文件就算内容没有任何改变,由于是该模块的hash
发生了改变,其css
文件的hash
也会随之改变。这个时候我们就可以使用contenthash
了,保证即使css
文件所处的模块里有任何内容的改变,只要 css 文件内容不变,那么它的hash
就不会发生变化。简单理解,自己变化才变化,只影响自己。
安装插件
1 | npm install html-webpack-plugin -D |
补充
该插件将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件。它自动会配合optimization.splitChunks
的配置。假如单独配置了element-ui
作为单独一个bundle
,它会自动也将它的样式单独打包成一个 css 文件,不会像以前默认将第三方的 css 全部打包成一个几十甚至上百 KB 的app.xxx.css
文件了。
1 | //打包 css 之后查看源码,我们发现它并没有帮我们做代码压缩 |
建议输出文件名需要使用hash的时候使用contenthash
而不是hash
。否则每次改动一个xx.js
文件,它对应的 css 虽然没做任何改动,但它的 文件 hash 还是会发生变化。
optimization.splitChunks
代码分包策略:按照体积大小、共用率、更新频率重新划分包,使其尽可能的利用浏览器缓存。(不过需要注意的是不需要细分太多包,解决了一部分问题,同时也带来了页面请求时候加载太多会阻塞的问题)。
我们希望更新频率低的抽出成单个文件,并且一直走缓存(它们的js与css)。
类型 | 共用率 | 使用频率 | 更新频率 | 例子 | |
---|---|---|---|---|---|
基础类库 | 高 | 高 | 低 | vue/vue-router等 | |
UI库 | 高 | 高 | 中 | Element-UI等 | |
组件 | 高 | 高 | 中 | 自定义组件等 | |
业务代码 | 低 | 高 | 高 | 业务逻辑等 |
补充一下mainfest与vendor.js
- vendor.js: 第三方库,一般是 node_modules里面的依赖进行打包
- manifest: runtime代码。浏览器运行时,webpack用来连接模块化的应用程序的所有代码(我的理解就是它就是实现模块化的代码)
manifest.js
长这样,这部分其实不怎么会变化,应该使用缓存
1 | (function(modules) { // webpackBootstrap |
optimization配置
1 | optimization: { // 抽离共用部分 |
配置了分包策略后,tree shaking失效了,需要使用一个支持删除死代码的压缩器。使用install terser-webpack-plugin
插件
编辑webpack.prod.js文件
1 | ... |
最后的package.json文件
1 | { |
编辑webpack.base.js文件
1 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); |
编辑webpack.dev.js文件
1 | const webpackMerge = require("webpack-merge"); |
编辑webpack.prod.js文件
1 | const webpackMerge = require("webpack-merge"); |
编辑config.js文件
1 | module.exports = { |
编辑.babelrc文件
1 | { |
编辑postcss.config.js文件
1 | module.exports = { |
编辑utils.js
1 | const path = require('path'); |
DLLPlugin
DLL:动态链接库
- 将网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中,一个动态链接库中可以包含多个模块
- 当需要导入的模块存在于某个动态链接库中时,这个模块不能再次被打包,而是去动态链接库中获取
- 页面依赖的所有动态链接库都需要被加载
也就是说webpack打包时,有一些框架代码是基本不变的,比如说vue、vue-router、vuex、axios、element-ui 等,这些模块也有不小的 size,每次编译都要加载一遍,比较费时费力。
没有选择使用DLLPlugin
也没有选择AutoDllPlugin插件,虽然减少了大量的配置文件,但是路径容易出问题,加速也不明显。
最后选择HardSourceWebpackPlugin插件:
配置之前打包时间大概 4916ms
配置文件,很简单的配置
1 | const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); |
文档说需要打包两次,才能看出来效果,那就看第二次构建的效果,打包时间大概 2577ms
HappyPack
由于大量文件需要解析和处理,当文件数量多的时候,webpack构建慢的问题会显得更为严重。运行在Node环境的webpack是单线程模型,不能同时处理多任务。而HappyPack能让webpack做到这一点。它将任务分解给多个子进程并发执行,子进程处理完后再将结果送给主进程。
然而项目较小时,多线程打包反而会使打包速度变慢,所以这边只是补充代码,项目没有使用。
配置文件(这种使用方法下一定要在根目录下加.babelrc
文件来设置babel
的打包配置)
1 | const HappyPack = require('happypack'); |
打包分析优化报表
1 | npm install webpack-bundle-analyzer -D |
配置
1 | ... |
可以从图中分析哪些模块太大,和如何优化。
比如这边lodash,我只使用了一个方法,有60多kb,因为在引入的时候只是引入了lodash的某个方法
1 | import cloneDeepWith from 'lodash/cloneDeepWith'; |
如果变成这样,会导入整个lodash,有500多kb
1 | import {cloneDeepWith} from 'lodash'; |
路由懒加载
构建配置
1 | ... |
路由文件配置
1 | import Vue from 'vue'; |
其中有遇到个报错,关于hard-source-webpack-plugin
插件的
解决方案:删除node_modules/.cache
文件
Vue
先安装vue与vue-router
1 | npm install vue vue-router -S |
略
接入ELementUI
安装
1 | npm i element-ui -S |
接入axios
安装
1 | npm install axios -S |
封装一下
1 | import axios from 'axios'; |
devServer.proxy
当您拥有单独的API后端开发服务器,并且希望在同一域上发送API请求时,代理某些URL可能会很有用。也就是解决跨域问题。
deveServer配置
1 | ... |
后端服务
1 | const koa = require('koa') |
项目地址
https://github.com/qinhanwen/webpack4-babel7-vue2
续集
参考
webpack 、manifest 、runtime 、缓存与CommonsChunkPlugin