由于篇幅限制,目前已转移至专栏更新:https://blog.csdn.net/YaoDeBiAn/column/info/30815
知乎文章地址:https://zhuanlan.zhihu.com/p/42424686
闲暇之余做的总结,仍在更新中,有错误或不足的地方请大家指正与分享见解,谢谢。
一.准备工作
1.初始化项目目录:npm init -y
2.建立以下的目录结构:
--dist
--src
----index.js
--package.json(初始化后生成)
3.完成基本的webpack配置:
首先,安装基本的webpack包:
npm install --save-dev webpack webpack-cli
接着,进行简单的webpack配置:
在根目录下创建webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
4.在package.json文件的scripts属性下配置npm脚本:
"scripts": {
...
"build": "webpack"
}
5.这个时候我们只要在终端输入npm run build就可以对src目录下的index.js文件打包,在dist目录下生成bundle.js文件。
二.ES6的babel转码配置
1.安装babel-preset-env包:npm install –save-dev babel-preset-env
2.在根目录下创建.babelrc文件:
{
"presets": [
"env"
],
"plugins": []
}
3.为了检验我们的配置是否成功,我们再安装babel-cli包:npm install --save-dev babel-cli,然后我们在根目录下创建一个babel_test文件用于测试,如下:
--babel_test
----test.js
test.js:
let arr = [1, 2, 3];
console.log([...arr]);
并在package.json文件中添加npm脚本:
"scripts": {
...
"build": "webpack",
"babel": "babel ./babel_test/test.js -o ./babel_test/res.js"
}
该脚本的作用就是将test.js进行转码,并将转码后的内容存储到res.js文件中。
在终端执行npm run babel则会在相同目录下生成res.js:
"use strict";
var arr = [1, 2, 3];
console.log([].concat(arr));
该文件已经转码成功,说明我们的配置是正确的。
三.安装简单的loader解析
webpack中,有一种操作就是在“.js”文件中引入非javascript资源,所以在将其打包的过程中,我们需要某些loader解析器对相关的资源进行解析。
首先我们先来看看引入css资源:
安装style-loader和css-loader两个loader:npm install --save-dev style-loader css-loader
webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': './src'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
}
这个时候我们就可以往入口文件(index.js)中import './style.css'。现在,当该模块运行时,含有css字符串的<style>标签,将被插到html文件的<head>中。废话不多说,实栗说话:
/src/style.css:
.hello {
color: red;
}
/src/index.js
import './style.css'
我们对index.js进行打包:npm run build,便在dist目录下产生了bundle.js
为了验证我们的样式是否已经打包成功,我们在dist目录下创建一个index.html文件,并将bundle.js文件引入进来:
/dist/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack_all</title>
</head>
<body>
<p class="hello">hello world</p>
<script src="./bundle.js"></script>
</body>
</html>
这个时候我们看到在页面上呈现红色字体的“hello world”:
小伙伴可能会说:“这不对啊,明明之前说css文件中的样式将会通过style标签插入到html文本中,但是上面的index.html只是将bundle.js插入进去而已。”
别急,上图说话:
看到没有,当我们在浏览器中打开该index.html文件,则会发现包含内容的style标签已经被插入到页面中了。也就是说,通过style-loader和css-loader对入口文件进行打包之后,我们可以通过在页面中引入bundle.js的方式通过bundle.js来插入style标签。(之后有时间想研究一下其中的原理,小伙伴们有知道的可以分享一下哦,想想大概也就是通过js来创建style标签,然后插入)
那如果我们想要导入图片呢?这个时候我们就可以用file-loader。
npm install --save-dev file-loader
webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': './src'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
}
现在,当你 import MyImage from './my-image.png',该图像将被处理并添加到 output 目录,并且_ MyImage 变量将包含该图像在处理后的最终 url。当使用 css-loader 时,如上所示,你的 CSS 中的 url('./my-image.png') 会使用类似的过程去处理。loader 会识别这是一个本地文件,并将 './my-image.png' 路径,替换为输出目录中图像的最终路径。html-loader 以相同的方式处理 <img src="./my-image.png" />。
现在我们向项目中添加一个图像:
现在我们在/src目录下面添加一张图片:
/src/Hydrangeas.jpg:
在index.js中导入该图片:
import Icon from './Hydrangeas.jpg'
现在我们只是将图片导入进来并没有将它使用,故而我们还要添加以下的代码:
function component() {
let element = document.createElement('div');
// 将图像添加到我们现有的 div。
let myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
return element;
}
document.body.appendChild(component());
然后打包:npm run build
打开index.html,呈现以下的页面:
实际上,在打包的过程中我们也把该图片打包到出口目录中,如下:
只不过图片的名字已经改成了随机串,同时页面中引用的图片也是打包后的这张图片,如下:
这样来说,其实我们也就可以在页面中引用这张图片,不过有一个问题:我们每次打包完之后图片的名称都不一样,也就说如果我们需要在页面中直接引用该图片,我们就需要每次改变在页面中引用的图片名,这是特别麻烦的一件事。有了问题就需要解决,不过这个问题我们把它放到讲plugin(插件)的时候再来解决。
现在我们已经将普遍会用到的两种基本的资源通过loader来进行相应的解析操作,但是各位小伙伴们可能没有发现,就是我们之前配置的babel转码在webpack中有用到吗?对于这个问题,我们先来看看被打包后的文件bundle.js:
从上面的图片可以看到有Symbol的字串,小伙伴们会很肯定地觉得webpack打包的时候并没有使用我们之前的配置对代码进行相应的转码。不过我们不应该这样看,因为我们目前的配置确实不能将Symbol进行转码,要对它进行转码还需要配置其他的一些插件。不过实际上我们也确实没有对代码进行转码处理,如果小伙伴想要验证它没有转码,很简单,就是在我们的路口文件中添加“let arr = [1, 2];console.log([...arr]);”,然后在打包之后我们在打包后的文件中搜索console.log找到我们上面console.log转码后的部分,然后就能看到了,小伙伴们可以去试试,这里我们就不演示了。
接下来我们来了解下如何在打包的时候对代码进行相应的转码:
首先我们要安装babel-loader包:npm install --save-dev babel-loader
然后在webpack.config.js中添加babel-loader规则:
module: {
rules: [
...
{
test: /\.js$/,
use: [
'babel-loader'
]
}
]
}
这里我们先打断一下,我们先来看看官方是怎么做的:
上面是说还需要安装babel-core包,该包是babel的核心依赖包,babel的核心api都在这里面。
废话就不多说了,我们继续我们之前的操作,我们来看看没有安装babel-core能不能成功(很显然是不能的,毕竟官网要下载babel-core包,也就说明babel-loader是建立在babel-core的基础上来实现的),先来看看栗子:
先在我们的入口文件,也就是index.js上,我们加入两条语句:
let arr = [1, 2, 3];
console.log([...arr]);
然后我们打包生成bundle.js文件,在该文件中查找console.log:
咦???语句已经被转码了,官网耍我们吗???
别急,小伙伴们记得之前我们为了测试还安装了babel-cli吗,其实目前是这个在起作用,不信我们就把这个包给删了。我们先把package.json中相应的模块删除,然后删除node_modules文件夹,然后再npm install一下。(个人不喜欢用uninstall来删除包,因为之前有过几次在通过这个命令删除包之后,发现项目就出问题了)
完成上面的步骤,我们进行打包,发现出问题了。。。:
呜呜呜。。。以后还是别太高看自己,官方不会骗人的,只能说你能力还不足而已。。。
然后我们再把babel-core进行安装之后就和刚刚转码是一样的,另外为了方便检测我们仍然把babel-cli重新安装,至于为什么babel-core和babel-cli都可以,其中的原理小伙伴们可以自行去研究一下。
之前在网上看到别人写的只安装webpack和babel-core就能进行转码简直就是扯淡,连最基本的babel-preset-XX都没有,希望大家都尝试去看看官方文档,以官方为准,或者自己多动手试试,不要被网上一些自以为是的人写的文章所误导。
四.plugins(插件)
loader被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
想要使用一个插件,你只需要require()它,然后把它添加到plugins数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new操作符来创建它的一个实例。
这里我们只简单地讲几个插件。
1.HtmlWebpackPlugin
这个插件非常有用,它会生成一个html文件,并将打包后的文件添加到进去,也就省去了我们手动去创建去添加,特别是我们打包的文件名可能经常会更改,这时这个插件就会非常有用。
现在我们来简单演示一遍:
首先安装该插件:npm install --save-dev html-webpack-plugin
在webpack.config.js中进行配置,添加plugins属性如下:
plugins: [
new HtmlWebpackPlugin({
title: 'yaodebian',
filename: 'index.html',
template: './src/index.html',
inject: true
})
]
当然别忘了在文件开头对该插件进行引入:const HtmlWebpackPlugin = require('html-webpack-plugin');
上面我进行了某些配置:title、filename、template、inject
title: 其实就是html文件中title标签的内容,上面的title配置其实会被template中指定的html文件配置。在没有配置template属性时,自动生成的html文件的title标签字段就是该title属性的值。
filename: 指定生成的html文件的位置,其相对于出口目录而定;
template: 模板就是用来copy的,该属性就是指定一个html文件作为将会生成的html文件的模板,相对于webpack配置文件目录而言,它的作用就是生成的文件内容将会在template指定的文件内容基础上再将打包后的bundle.js文件添加进去组合成一个新的html文件。
- inject: 它有四个值true、body、head、false
- true 默认值,script标签位于html文件的 body 底部
- body script标签位于html文件的 body 底部
- head script标签位于html文件的 head中
- false 不插入生成的js文件,这个几乎不会用到的
废话不多说,举个栗子:
像上面配置中,我是在src目录下添加了一个index.html并作为模板,index.html中的内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack_all</title>
</head>
<body>
<p class="hello">hello world</p>
<img src="./Hydrangeas.jpg" alt="">
</body>
</html>
我们再来打包一下,结果可以看到dist目录下生成了一个新的index.html覆盖了之前的index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack_all</title>
</head>
<body>
<p class="hello">hello world</p>
<img src="./Hydrangeas.jpg" alt="">
<script type="text/javascript" src="bundle.js"></script></body>
</html>
看到了吧,就是将我们的模板添加上打包后的bundle.js文件,这样我们就不用自己创建html文件并手动插入script。
不过有一个问题,这其实就是我们之前在讲loader的时候讲的那个问题,用file-loader引入的一张图片,并会在出口目录下会生成一张以图片内容的MD5哈希值命名的图片。这个时候我们要在html文件中通过img标签来引用与呈现这张图片时就需要手动更改标签中src属性中图片的名字。我们想要让它自动更改应该要怎么实现呢?
自己在网上搜索了下,找到一个国人写的loader: html-withimg-loader
首先,我们安装该loader:npm install --save-dev html-withimg-loader,然后我们在webpack.config.js添加如下配置:
{
test: /\.html$/,
loader: 'html-withimg-loader'
}
最后在入口的js文件中引入我们作为html-webpack-plugin模板的html文件:
import './index.html'
这个时候我们进行打包,产生的index.html文件如下:
dist/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack_all</title>
</head>
<body>
<p class="hello">hello world</p>
<img src="bdf3bf1da3405725be763540d6601144.jpg" alt="">
<script type="text/javascript" src="bundle.js"></script></body>
</html>
我们看下dist目录下的文件结构:
看到了吧,这个时候img标签的图片名就和打包后的图片是一样的了。
不过,这个loader在平时应该不怎么会去用,这里只是针对之前遇到的问题使用这个loader。
2.clean-webpack-plugin
你可能已经注意到,由于过去的指南和代码示例遗留下来,导致我们/dist文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。
通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。让我们完成这个需求。
上面两段是官方的原话,可能有些小伙伴不理解。通俗的讲,假如我们dist目录下有一个hello.js文件,但是我们打包生成的文件中没有这个文件,那打包之后这个文件就是多余的,我们根本不会用到,所以我们需要通过某些手段在打包之前将dist目录清空。这里,我们用到了clean-webpack-plugin。
安装该插件:npm install --save-dev clean-webpack-plugin
在webpack.config.js文件中进行如下配置:
添加插件声明:const CleanWebpackPlugin = require('clean-webpack-plugin');
使用插件:new CleanWebpackPlugin(['dist'])
上面初始化插件配置时传入的参数就是包含了要清空的目录的数组,详细的配置可以去官网查看。
为了验证插件的效果,我们先在dist目录下创建一个hello.js的文件:
最后我们再进行打包:npm run build
结果如下:
说明插件已经生效了!!!
对于上面的应用,官方提出了一个叫做Manifest的概念:
这里就不赘述,小菜我也还没研究过,希望之后找个时间再另外单独研究并总结一下。同样也希望各位小伙伴或大大分享相关方面的知识或文章哦!!!
五.suorce map
当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。这并通常没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。
为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。
Source map有很多不同的选项可用,请务必仔细阅读它们,以便可以根据需要进行配置。
上面是官方的原话,本人懒,直接拷贝过来了。。。
对于source map的选项配置,请参考:https://www.webpackjs.com/configuration/devtool/
对于source map深层原理的理解,请参考阮一峰大大的文章:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
好了,现在来看栗子了,这里使用的是官方的栗子(其他的看心情再捣鼓捣鼓。。。似不似很欠扁。。。哈哈):
首先,我们在webpack.config.js中添加devtool选项:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.js$/,
use: [
'babel-loader'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
},
{
test: /\.html$/,
loader: 'html-withimg-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'yaodebian',
filename: 'index.html',
template: './src/index.html',
inject: true
}),
new CleanWebpackPlugin(['dist'])
]
}
然后先对入口文件进行一些修改,添加如下:
hahaha();
因为hahaha()之前我们并没有声明过,肯定会报错。我们先打包:npm run build
然后我们运行一下:
点进去看看:
我们看到,明明是23行出错,但是它却说21行报错,是source map解析出错了吗,不禁产生这样的想法。
我们在22行处添加consoe.log('yaodebian'):
import './index.html'
import './style.css';
import Icon from './Hydrangeas.jpg'
console.log('yaodebian');
let arr = [1, 2, 3];
console.log([...arr]);
function component() {
let element = document.createElement('div');
// 将图像添加到我们现有的 div。
let myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
return element;
}
document.body.appendChild(component());
consoe.log('yaodebian');
hahaha();
我们再次打包看看:
这次却是正确的,很好的标记出了错误,至于为什么之前的不准确目前不知道其原理,姑且先将它视作是souce map解析的问题吧,有知道的小伙伴请告知一下下。
之后看了几篇文章,对于devtool这个配置选项,不同的属性值会导致不同程度的souce map解析定位,以及会影响构建速度,不过并没有怎么看懂。这里就不再赘述了,涉及的内容比较多,想之后再单独抽时间总结一下。
接着上面的继续。。。还有好多知识点。。。
六.开发工具(自动编译代码)
每次要编译代码时,手动运行npm run build特别麻烦。
webpack中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:
1.webpack's Watch Mode(观察模式)
2.webpack-dev-server(提供一个服务器)
3.webpack-dev-middleware(是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 )
webpack's Watch Mode(观察模式)
上面是官方原话,没有看懂就先来看一个栗子:
首先在package.json中添加一个npm script脚本:
=================以下内容更新于2018.9.20======================
拖了将近一个月了,妈卖批的~~~
======================================================
"script": {
...,
"watch": "webpack –watch",
...
}
现在我们就可以在命令行中运行npm run watch启动观察模式,然后打开之前创建的index.html文件,报错:
这是因为之前为了检测souce map而设置的错误,我们把它去掉就好。
然后我们在src中的入口文件(index.js)文件中添加一句“console.log('watch mode')”,保存,会发现系统自动帮我们重新编译了(说明我们的配置生效了),重新刷新页面则会看到如下:
控制台上则多出了一句watch mode。
观察模式虽然能够达到自动编译代码的效果,但是其有一个缺点,就是只能帮我们自动重新编译,但不会帮我们重新刷新浏览器。如果想要在重新编译代码的同时刷新浏览器,则可以尝试使用webpack-dev-server。
另外,在执行watch mode后,会看到webpack编译代码却不会退出命令行,这是因为script脚本还在观察文件。
使用webpack-dev-server
webpack-dev-server提供一个简单的web服务器,并且能够实时重新加载(living reloading)。
首先我们需要安装相应的包:npm install –save-dev webpack-dev-server
然后在webpack.config.js中添加devServer选项:
module.exports = {
...,
devServer: {
contentBase: './dist'
},
...
}
以上的配置使得webpack-dev-server将在localhost:8080下建立服务,将dist目录下的文件,作为可访问文件。
最后再在package.json文件中添加script脚本:
"script": {
...,
"start": "webpack-dev-server --open",
...
}
现在,通过在命令行中运行npm start(注意这里是直接用npm start,一般我们都是使用npm run start,webpack-dev-server比较特殊,我们还是使用通用的npm run start以免出错),就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web服务器就会自动重新加载编译后的代码。
使用webpack-dev-middleware
webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。
接下来是一个webpack-dev-middleware配合express server的示例。
首先安装express和webpack-dev-middleware包:
npm install –save-dev express webpack-dev-middleware
接下来对webpack配置文件做一些调整,以确保中间件(middleware)功能能够正确启用:
webpack.config.js:
output:{
...,
publicPath: '/'
}
publicPath也会在服务器脚本用到,以确保文件资源能够在http://localhost:3000下正确访问,
我们稍后再设置端口号。下一步就是设置我们自定义的 express 服务:
项目根目录下我们添加一个server.js文件(server.js):
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
现在添加一个npm script:
"script": {
...,
"server": "node server.js",
...
}
七.tree shaking
添加一个通用模块:
src/math.js:
export function square(x) {
console.log('square');
return x*x;
}
export function cube(x) {
console.log('cube');
return x*x*x;
}
接着,更新入口脚本,并使用其中一个方法:
src/index.js:
...
import {cube} from './math.js';
console.log(cube(2));
...
现在我们运行我们的npm脚本npm run build,并检查我们输出的bundle:
注意,我们没有导入squre,但是它仍然被包含在bundle中,这个是为什么呢,为什么还会保留?小伙伴们可能会说“这还用问吗,现在根本就没有进行任何配置,自然不会帮我们进行优化”,真的是这样吗,我们继续看。
现在,我们在package.json文件中添加一个配置"sideEffects": false (这个配置的意思是将入口文件涉及到的文件都标识为不含副作用,以此来告知webpack,它可以安全地删除未用到的export导出)
然后输入命令npm run build进行打包操作,结果如下:
我擦,根本没有上面卵用,和文档上是一样的啊。~~~莫急莫急,我们接着来~~~
前方高能,胆小勿入~~~(以下黑色粗体字为本人自我臆想,切莫相信,切莫相信)
同样的,没有进行treeShaking,没有引用的函数还是包含在bundle.js文件中,按道理,如果它真的会优化,当我们设置"sideEffects": false,它便会进行优化打包,实际上它没有,那么真的没有吗?
其实上面的所有情况(包括不设置sideEffects配置的情况)webpack都会帮我们进行优化,操作treeShaking,至于为什么,这里要提一下:
还记得官方怎么说的吗?
Tree shaking必须要依赖于es6的import和export,针对export引用的文件以及真正使用到的部分进行优化处理,而我们在这之前进行了babel转码的操作,对于转码后的es5语法我们就不会使用到tree shaking。
是的,上面的纯属本人扯淡,其实事实并非如此,其实在加了sideEffects时,webpack确实已经将我们的代码进行tree shaking,我们继续来玩玩吧:
我们在index.js入口文件中添加这样几句,如:
function test() {
return 1234;
}
test();
咱打下包吧,然后搜索一下1234看看,结果:
看到没找不到,说明webpack已经启动了tree shaking并检测出上述代码是dead code(无用代码),故而将上述代码删除。
那~~~,为哈子我们import时的代码中square函数仍然保留呢?这个嘛~~~这个确实跟es6语法有关:因为我们之前不是用了babel吗,babel把es6转化为es5的过程中,会使得转化之后的代码产生副作用,故而上述math.js中的square会存在其中。
A:那~~~不是说sideEffects设为false会将代码标识为pure(纯的)吗,怎么还会有呢???
B:好说好说,看来这位同学没有理解其中的意思,该配置将代码标识为纯,就说明代码没有副作用,没有副作用的话,我们就可以删除那些看似无用的代码(根据webpack的解析规则),而不用担心误删了某些代码。Babel将es6转化为es5,而代码没有删除,说明转化后的代码不在tree shaking的无用代码队列中。
A:哦哦,貌似懂了~~~
所以呢,为了删除上面没有用到的square函数,我们需要去除掉babel的作用效果,即在webpack.config.js配置文件中注释掉babel的配置,如下:
接着我们重新打包文件:
看到没有,现在只剩下cube这个函数了。
好了,接下来我们来看看通过sideEffects来将某些文件标识为有副作用吧,这样一来,tree shaking就不会随便将该文件中“看似无用”的代码删除,而将它们保留下来,就是它会将“可能有副作用”的代码保留下来(具体的解析原理暂时没有了解过),那些确实无用的代码仍然会被删除。
其实,就是说,当我们将代码标识为sideEffects:false时,可能会误删具有副作用的代码,下面我们就来举一个栗子吧(目前sideEffects仍然为false):
我们在src目录中创建一个menu.js文件:
function Menu() {
}
Menu.prototype.show = function() {
}
Array.prototype.unique = function() {
// 将 array 中的重复元素去除
}
export default Menu;
并在index.js中将其引入:
import Menu from './menu.js';
然后我们打包看看:
居然找不到之前的Array.prototype.unique = function() {...},心里妈卖批“这句是我们要的,是有副作用的,你凭啥删了”。。。
然后tree shaking小姐姐回了一句:“就凭你没告诉我啊,我怎么知道这句是有用的”
“好了好了,下次执行之前,我跟你讲一句好了~~~”
现在我们将sideEffects设置为一个数组如:
看到了没,出来了吧,所以如果我们不能保证我们的代码是纯的(pure),也就是没有副作用,那么我们便要将相应的文件标识为有副作用,以免某些代码会被误删。上面的只是找到的一个特例,我们平常也不会那样用,我们会import其中的menu,并执行它,这样Array.prototype将会被引用进来,也就不需要上面标识了。具体还有什么比较切实际的栗子目前暂不可知,有知道的哥们或者小姐姐请留言分享一下。
好了,基本上tree shaking就讲的差不多了,不过还有一个东西要讲,就是webpack4(上面测试的版本是4.16.5,是默认支持的,前面几个版本就不知道了)默认是带有tree shaking功能的,也就是说我们不用配置sideEffects其实已经有tree shaking了,sideEffects只是起一个标识的作用。下面我们来测试一下吧:
将sideEffects配置去掉,然后打包一下:
我们发现没有square,而只有cube,另外我们查一下之前添加的test函数是否存在,就是这个函数:
结果:
所以,看到了吧,tree shaking生效了。
另外,除了默认具有tree shaking之外,还默认标记所有代码都有副作用,为了验证这个猜想,我们进行一下检测:
我们先把上一次打包后的bundle.js保存到我们的src目录下;
然后在pakage.json文件中配置sideEffects为:["./src/*.*"],这样就相当于标识多有文件有副作用,我们只要对比此次打包后的文件是否和上一次打包的文件相同,就知道我们的猜想是否是正确的:
仔细对比一下就会发现它们是相同的,也就验证了我们的猜想。
这一节也讲了很久了,最后提醒一下,千万要将引入的.css等一些文件标识为有副作用,不然css文件的引入会被删除,也就不会应用于html文件中。
更新中。。。