基于webpack4.0手动搭建web项目(更新中)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/YaoDeBiAn/article/details/81865494

知乎文章地址: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文件中。


更新中。。。

猜你喜欢

转载自blog.csdn.net/YaoDeBiAn/article/details/81865494