从零开始学 Webpack 4.0(三)

911 阅读19分钟

原文本人写于 2022-05-03 22:55:09

一、前言

本篇文章会继续讲述 Webpack 的核心概念,涉及的很多内容都是我们实际开发中需要用到的。

因为这个笔记是一个系列的文章,一些代码及配置的文件我会直接拿上篇文章里面的继续编写,不会每次都从零开始介绍每个文件包含内容,但只要有阅读之前的文章是可以正常学习的,不会莫名其妙新增内容。有需要相关文件的在每篇文章底部提供的 Git 仓库里取即可。

项目结构

本文会以 demo03 作为项目文件夹,各文件直接拿之前项目 demo02 的,接下来做些修改。

20220501_01.jpg

package.json

{
  "name": "demo03",
  ...
}

package-lock.json

{
  "name": "demo03",
  ...
}

webpack.config.js

...
module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      ...
      title: 'demo03自定义title'
    }),
    ...
  ],
  ...
}

index.js(原文件内容清空)

console.log('hello!')

安装依赖

npm ci

运行打包命令

npm run bundle

打包成功,dist 目录下 index.html 在浏览器可以正常运行,查看控制台打印结果。

20220501_02.jpg

二、Entry 与 Output 的基本配置

(1)我们现在 Entry 与 Output 的配置是:

webpack.config.js

...
module.exports = {
  ...
  entry: {
    main: './src/index.js'
  },
  ...
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

这样去配置,Webpack 在进行打包的时候会在项目根路径下生成 dist 目录并在里面输出 main.js。

(2)

webpack.config.js

...
module.exports = {
  ...
  entry: './src/index.js',
  ...
  output: {
    // 清空
  }
}

这样去配置,Webpack 在进行打包的时候同样会在项目根路径下生成 dist 目录并在里面输出 main.js。

因为 entry 默认就是 main,而 output 没有去指定输出的文件名,所以打包输出的文件名是 main.js。至于 dist 目录同样也是默认的配置。

(3)我们打包两次 index.js

webpack.config.js

...
module.exports = {
  ...
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  ...
  output: {
    filename: 'main.js'
  }
}

运行打包命令,会发现报错了。因为我们指定了两个打包的入口文件,但在 output 里只指定了一个文件名。

20220501_03.jpg

(4)通过使用占位符来指定对应的打包输出文件名

webpack.config.js

...
module.exports = {
  ...
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  ...
  output: {
    filename: '[name].js'
  }
}

打包成功,并且 HtmlWebpackPlugin 这个插件会在打包结束后生成的 index.html 里把这两个 js 文件都进行引入。

20220501_04.jpg 20220501_05.jpg

(5)在实际开发中,我们可能把 index.html 这个文件提供给后端当入口,其他引入的文件或者说静态资源上传到 cdn 供 index.html 使用,这时候 index.html 引入的文件路径就需要在前面拼接上 cdn 的地址。

可以配置 output 的 publicPath:

webpack.config.js

...
module.exports = {
  ...
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  ...
  output: {
    publicPath: 'http://cdn.com.cn',
    filename: '[name].js'
  }
}

运行打包命令,打包成功,查看 dist 目录下的 index.html:

20220501_06.jpg

(6)为了接下来的学习,配置还原。

webpack.config.js

...
module.exports = {
  ...
  entry: {
    main: './src/index.js'
  },
  ...
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

三、SourceMap 的配置

使用场景

在开发中总会遇到各种报错的问题,清晰明了的报错信息能够帮助我们快速定位错误,解决问题。

首先我们把 mode 为 'development' 时 SourceMap 的默认配置项关了,mode 为 'production' 时不会有 SourceMap 的默认配置项。

webpack.config.js

...
module.exports = {
  mode: 'development',
  // 关闭 'development' 默认开启的 SourceMap
  devtool: 'none',
  ...
}

现在我们来尝试引发一个错误,修改 index.js。

index.js

consele.log('hello!')

运行打包命令,打包成功,index.html 放入浏览器运行,查看控制台报错。

20220501_07.jpg

点击控制台错误信息右侧显示的 main.js:96,我们会发现它定位代码到 index.js 打包后 main.js 里的位置。

20220501_08.jpg

这样的报错信息代码定位,要是在平时的开发中肯定是不能够及时解决问题的。

我们需要的是它能够定位到源代码 index.js 里面的报错位置,这时候就需要用到 SourceMap。

SourceMap 的作用

SourceMap 是源代码与目标代码之间的一个映射,它会在 Webpack 打包的时候去构建一个映射关系。

当浏览器控制台说 main.js 第 96 行的代码报错,SourceMap 这个映射关系它知道 dist 目录下 main.js 第 96 行实际上对应的是 src 目录下 index.js 中的第一行。

具体配置介绍

开启 SourceMap

webpack.config.js

...
module.exports = {
  mode: 'development',
  devtool: 'source-map',
  ...
}

运行打包命令,打包成功,index.html 放入浏览器运行,查看控制台报错。

20220501_09.jpg

点击控制台错误信息右侧显示的 index.js:1,这次的代码报错就直接定位到源代码 index.js 里去了。

20220501_10.jpg

这样我们就能准确定位到代码报错的位置。

但设置了 'source-map',打包速度是会变慢的,因为它需要去构建一个映射关系,dist 目录下会多出一个 main.js.map 文件,这里面就是一个映射的关系。

devtool 对 SourceMap 的配置不仅仅只有 'source-map' 这一种,参考官方文档可以看到可选项是很多的,下面我们来简单了解一下。

首先放一份官方的介绍:

devtool构建速度重新构建速度生产环境品质(quality)
(none)++++++yes打包后的代码
eval++++++no生成后的代码
cheap-eval-source-map+++no转换过的代码(仅限行)
cheap-module-eval-source-mapo++no原始源代码(仅限行)
eval-source-map- -+no原始源代码
cheap-source-map+oyes转换过的代码(仅限行)
cheap-module-source-mapo-yes原始源代码(仅限行)
inline-cheap-source-map+ono转换过的代码(仅限行)
inline-cheap-module-source-mapo-no原始源代码(仅限行)
source-map- -- -yes原始源代码
inline-source-map- -- -no原始源代码
hidden-source-map- -- -yes原始源代码
nosources-source-map- -- -yes无源代码内容

+++ 非常快速, ++ 快速, + 比较快, o 中等, - 比较慢, - - 慢

其中一些值适用于开发环境,一些适用于生产环境。对于开发环境,通常希望更快速的 source map,需要添加到 bundle 中以增加体积为代价,但是对于生产环境,则希望更精准的 source map,需要从 bundle 中分离并独立存在。

当设置为 'inline-source-map',一样有这个映射关系,而且 dist 目录下不会新增 map 文件。这个 map 文件会直接通过 sourceMappingURL,以 base64 的形式写在 main.js 里面。

不加 'cheap' 时,代码报错会告诉你精确到哪一行哪一列,但这样的映射比较耗性能。当设置为 'inline-cheap-source-map',表现与 'inline-source-map' 基本一致,只是代码报错不会具体到哪一列,只会具体到哪一行,这样做打包的性能就会得到提升。使用 'cheap' 的话,它只会映射 main.js 的代码,至于里面引入的第三方文件或者 Loader 不会去映射,它只管业务代码。

如果想要 'cheap' 去管一些第三方文件以及 Loader 的代码报错,可以加一个 'module',写做 'inline-cheap-module-source-map'。

'eval' 是打包速度最快的一种方式,它的报错能够具体到源代码的哪一行,而且不会产生 map 文件,它会在 main.js 里通过 eval 这种 js 的支持形式来生成 SourceMap 的对应关系。这种方式是执行效率最高,性能最好的一种打包方式,但是对于复杂代码的情况下,它的提示方式可能并不全面。

这些通过 '-' 隔开的关键词似乎是有一定顺序排列的,我们需要记住每个关键词的大概作用,尽量以官网为准选择合适的组合。

配置还原

webpack.config.js

...
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  ...
}

index.js

console.log('hello!')

课程内推荐的最佳实践(可以参考)

开发环境中建议使用 'cheap-module-eval-source-map',这种方式提示的错误比较全,打包速度也比较快。

至于生产环境一般不需要这个 SourceMap 的映射,可以直接不配 devtool。但当出现问题需要线上排错的时候,可以配置 'cheap-module-source-map',提示效果更好一些。

四、WebpackDevServer 的使用

现在我们要查看代码的改动效果,首先要运行打包命令,然后把 dist 目录下 index.html 放入浏览器运行才能看到效果,每次这样操作比较麻烦。

我们希望修改 src 目录下的代码时,dist 目录下的内容会自动重新打包。

(1)实现方法一:

修改 package.json,新增 scripts 脚本命令 watch。

package.json

{
  ...
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch"
  },
  ...
}

加了 watch 的意思是 Webpack 会帮我们监听要打包的文件,只要文件发生变化,它就会自动的重新打包。

这时候我们每次修改完源代码,刷新下浏览器就能看到最新的效果。

运行 watch 这个命令

npm run watch

打包成功,dist 目录下照常输出文件,命令行处于运行服务中的状态。

把 index.html 放入浏览器运行,观察控制台打印结果。

20220502_01.jpg

然后对 index.js 打印内容进行修改

index.js

console.log('hello world!')

刷新浏览器页面,发现控制台打印结果确实变了。

20220502_02.jpg

在命令行 ctrl + c 中止进程

但它不是 http 协议,在平时的开发中不方便进行一些 ajax 请求发送之类的操作。

(2)实现方法二(推荐)

当我们希望第一次输入命令时,Webpack 去进行打包,监听打包文件的变化,同时自动把浏览器打开,并且去模拟一些服务器上的特性。

这时候我们就需要借助 WebpackDevServer,它能够帮我们启动一个 http 服务器。

安装 webpack-dev-server

npm install webpack-dev-server@3.1.10 -D

配置 devServer

webpack.config.js

...
module.exports = {
  entry: {
    ...
  },
  devServer: {
    // 服务器要启动在哪一个文件夹下,也就是服务器根路径。
    contentBase: './dist',
    // 自动帮你打开浏览器去访问
    open: true,
    // 配置端口号,默认就是 8080 端口。
    port: 8080
  },
  ...
}

新增 scripts 脚本命令 start

package.json

{
  ...
  "scripts": {
    ...
    "start": "webpack-dev-server"
  },
  ...
}

运行命令,启动服务。

npm run start

这时候我们可能会遇到一个报错,原因大概是 webpack-cli 与 webpack-dev-server 的版本不兼容导致的。

20220502_03.jpg

卸载 webpack-dev-server

npm uninstall webpack-dev-server

换个版本安装

npm install webpack-dev-server@3.11.0 -D

再次运行命令,服务启动成功,浏览器自动打开 localhost:8080 访问 index.html,控制台打印 "hello world!"。

对 index.js 打印内容进行修改

index.js

console.log('hello WebpackDevServer!')

观察浏览器控制台变化,发现打印内容确实变成了 "hello WebpackDevServer!"。

同时,我们也会发现 dist 目录不存在了,这是因为 WebpackDevServer 是本地开发使用的,它的打包内容在内存里,这是为了提高打包的速度。如果需要打包生成相关的目录及文件,用之前的打包命令即可。

(3)实现方法三:

尝试写个服务器监听 src 目录下的文件,当该目录下内容有所改变,它就会自动去重启服务,更新网页上的内容,例如通过 Express 搭建一个 http 服务器去进行相关的编写。这里只是提供个思路给大家,毕竟实现方法二才是我们平时开发中常用的。

五、Hot Module Replacement 热模块更新

通过使用 WebpackDevServer,只要文件发生变化就会被重新打包并且刷新浏览器页面,但当我在页面上执行了操作 a,这时候我修改 b 的相关代码,浏览器刷新,页面重新渲染,导致之前操作 a 也给清除了,这种时候我们就需要用到 Hot Module Replacement,简称 HMR。

css 样式举例

在 src 目录下新建 style.css

style.css

div:nth-child(odd){
  background: skyblue;
}

修改 index.js

index.js

import './style.css'

var btn = document.createElement('button')
btn.innerHTML = '新增'
document.body.appendChild(btn)

btn.onclick = function() {
  var item = document.createElement('div')
  item.innerHTML = 'item'
  document.body.appendChild(item)
}

运行命令,启动服务。

npm run start

这时候页面上有个新增按钮,每点击按钮一下就会往页面上增加一个 div,排列顺序为偶数的 div 会有个背景颜色。

20220502_04.jpg

修改背景颜色

style.css

div:nth-child(odd){
  background: yellow;
}

这时候返回浏览器查看,原来生成的 div 因为页面自动刷新而被清空了。

20220502_05.jpg

再次点击新增按钮,新增的 div 背景颜色确实改变了。

20220502_06.jpg

当我们再次修改 style.css,这时候页面又会被刷新,导致刚刚新增的 div 再次被清空,这样反复清空再点击按钮去新增才能看到样式效果,在开发中效率是有些低的。

开启 HMR

webpack.config.js

...
// 引入 Webpack
const webpack = require('webpack')

module.exports = {
  entry: {
    ...
  },
  devServer: {
    ...
    // 开启 Hot Module Replacement
    hot: true,
    // 即便 HMR 的功能没有生效,也不让浏览器去重新刷新页面。
    // 该选项可配可不配,一般会配上。
    hotOnly: true
  },
  module: {
    ...
  },
  plugins: [
    ...
    // 引入 Webpack 里自带插件
    new webpack.HotModuleReplacementPlugin()
  ],
  ...
}

中止进程,重启服务,这时候再去进行上面的操作,发现不会每次都把页面上新增的 div 给清空了。 开启 HMR 后,不会去刷新页面导致页面之前渲染的内容被清除,而是把发生改动的样式代码给替换掉。

js 业务代码举例

为了方便举例,我们先把 HMR 关了。

webpack.config.js

...
module.exports = {
  entry: {
    ...
  },
  devServer: {
    ...
    // hot: true,
    // hotOnly: true
  },
  module: {
    ...
  },
  plugins: [
    ...
    // new webpack.HotModuleReplacementPlugin()
  ],
  ...
}

在 src 目录下新建 counter.js、number.js 。

counter.js

function counter() {
  var div = document.createElement('div')
  div.setAttribute('id', 'counter')
  div.innerHTML = 1
  div.onclick = function() {
    div.innerHTML = parseInt(div.innerHTML) + 1
  }
  document.body.appendChild(div)
}

export default counter

number.js

function number() {
  var div = document.createElement('div')
  div.setAttribute('id', 'number')
  div.innerHTML = 1000
  document.body.appendChild(div)
}

export default number

修改 index.js

index.js

import counter from './counter.js'
import number from './number.js'

counter()
number()

运行命令,重启服务,页面上会有两个数字,数字 1 点击时会自增。

20220502_07.jpg

一直点击数字 1 使其自增到 10

20220502_08.jpg

修改 number.js,显示到页面的数字从 1000 改为 2000。

number.js

function number() {
  ...
  div.innerHTML = 2000
  ...
}
...

这时候 WebpackDevServer 会让浏览器刷新页面,导致上面的数字 10 再次变回一开始的数字 1。

20220502_09.jpg

我们想要达到的效果是当 number.js 里面的代码发生改变,那么 Webpack 只需要把 number.js 的代码替换掉,而不是直接刷新整个页面,导致 counter.js 的操作也被清除了,这时候就需要用到 HMR。

开启 HMR

webpack.config.js

...
module.exports = {
  entry: {
    ...
  },
  devServer: {
    ...
    hot: true,
    hotOnly: true
  },
  module: {
    ...
  },
  plugins: [
    ...
    new webpack.HotModuleReplacementPlugin()
  ],
  ...
}

和 css 不一样的地方在于 js 想要实现 HMR 的效果,还需要去编写一些相关的代码。

修改 index.js

index.js

import counter from './counter.js'
import number from './number.js'

counter()
number()

// 当项目开启了 HMR 的功能就会执行
if (module.hot) {
  // 当 number.js 这个文件发生变化就会触发后面的函数
  module.hot.accept('./number.js', () => {
    // 清除原有页面元素
    var item = document.getElementById('number')
    document.body.removeChild(item)
    // 重新调用一次
    number()
  })
}

中止进程,重启服务。

20220502_09.jpg

再去进行上面的操作,点击数字 1 使其自增到 10,接着去修改 number.js 显示到页面的数字从 2000 改为 3000,这时候页面并不会去进行刷新,而是直接把 number.js 的 2000 更改为 3000。

20220502_10.jpg

至于 css 为什么不需要加一段代码进行处理,这是因为 css-loader 底层已经实现了。

就像用 Vue 写代码也有 HMR 的效果,是因为 Vue Loader 里面内置了相关代码的编写。

当项目中引入比较偏的文件类型,这些文件的 Loader 并没有内置 HMR 的效果,这时就需要我们添加 module.hot.accept() 这样的代码去处理。

HMR 实际上就是局部内容的更新。

注意

当 webpack.config.js 这个配置文件做了改动,是必须重启服务才能生效的。

六、使用 Babel 处理 ES6 语法

首先我们在 index.js 里面编写一些 es6 代码。

index.js(原文件内容清空)

const arr = [
  new Promise(() => {}),
  new Promise(() => {})
]

arr.map(item => {
  console.log(item)
})

因为需要观察打包生成文件,就不用 WebpackDevServer 去启动服务了,使用之前的打包命令。

npm run bundle

把 dist 目录下 index.html 放入浏览器运行,观察控制台打印结果,正常输出两个 promise 对象。

20220502_11.jpg

查看 dist 目录下 main.js 内最后的代码,就是我们刚刚写的 es6 代码。

20220502_12.jpg

这种 es6 的代码,在我用的谷歌浏览器能够直接运行,是因为谷歌浏览器与时俱进,ES6 规范里的很多内容它都做了实现。但这段代码在一些低版本的浏览器,程序执行就会报错。

这时候我们就需要借助 Babel 来处理这些 es6 的代码,转换成 es5。

业务代码的处理

Babel 对 ES6 语法的处理需要区分一下使用场景,有一些配置是有差别的,我们首先看一下最常用的开发中业务代码的处理。

@babel/core 是 Babel 的一个核心库,能够让 Babel 去识别 js 代码里的内容,将 js 代码转换成 AST 抽象语法树,然后将 AST 抽象语法树编译转换成新的语法。

babel-loader 是 Babel 和 Webpack 做通信的一个桥梁,实际上 babel-loader 并不会帮我们把 js 文件里的 es6 语法翻译成 es5 语法,还需要借助一个 @babel/preset-env,这个模块内包含所有 es6 转换成 es5 的翻译语法规则,比如 let、箭头函数等等。

安装 babel-loader、@babel/core、@babel/preset-env

npm install babel-loader@8.0.4 @babel/core@7.2.0 @babel/preset-env@7.2.0 -D

使用 babel-loader 并进行相应配置

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        // 当 js 文件处于 node_modules 目录下,不用 babel-loader 去处理。
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    ]
  },
  ...
}

运行打包命令,查看 dist 目录下 main.js 内最后的代码。

20220502_13.jpg

index.js 里的 const 变成 main.js 里的 var,证明原有的 es6 语法给转换成 es5 的语法了,但这样还是不够,main.js 里出现的 promise 以及 map 这些在低版本浏览器里还是不存在的。

这时候不仅需要用 @babel/preset-env 做语法上的转换,还要把这些缺失的变量或者函数补充到低版本的浏览器里,需要借助工具 @babel/polyfill。

安装 @babel/polyfill (注意是生产依赖)

npm install @babel/polyfill@7.0.0

安装后需要在所有代码运行前去引入 @babel/polyfill 来补充浏览器缺少的内容。

在引入前我们先来进行一次打包,观察打包文件的大小。

20220502_14.jpg

修改 index.js,在代码最上方引入 @babel/polyfill。

index.js

import '@babel/polyfill'
...

再次运行打包命令,观察打包文件的大小。

20220502_15.jpg

通过两次打包后的结果,我们可以观察到 main.js 的大小从 31.7kb 变成 890kb,这多的内容就是 @babel/polyfill 去补充一些低版本浏览器缺少的 es6 相关变量及函数,但在本项目中我们只需要它补充 promise 以及 map 的实现,其他不需要的不要补充进来。

需要给 @babel/preset-env 配置一个参数

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                // 新增配置
                // 当 @babel/polyfill 填充的时候,根据业务代码决定到底加什么。
                useBuiltIns: 'usage'
              }
            ]
          ]
        }
      }
    ]
  },
  ...
}

以及移除 index.js 里的 import '@babel/polyfill'。(当使用了 useBuiltIns: 'usage',它会自动在我们的业务代码里面去引入。)

再来进行一次打包,观察打包文件的大小。

20220502_16.jpg

main.js 从 890kb 变成 147kb,观察 main.js 内代码。

20220502_17.jpg

通过上图可以大概看出 @babel/polyfill 去填充的一些 es6 内容,这时候代码就可以在一些低版本的浏览器去运行了。

拓展一下,我们还可以这样配置 @babel/preset-env。

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                // 新增配置
                // 意思是这个项目打包后会运行在大于 67 版本的谷歌浏览器下,
                // Babel 会去判断是否需要在这些浏览器下进行语法的转换以及注入一些 es6 语法的实现。
                targets: {
                  chrome: '67'
                },
                useBuiltIns: 'usage'
              }
            ]
          ]
        }
      }
    ]
  },
  ...
}

想要进阶了解更多的可以查看 Babel 的官网相关文档及视频。

开发类库或第三方模块的处理

当在进行一些业务代码的开发,可以参考上面的 Babel 相关配置。但在开发一个类库或者第三方模块之类时,用 @babel/polyfill 其实是有问题的,它在注入 es6 语法相关的一些实现方案时,注入的是全局变量,污染了全局环境。

这时候我们需要更换一种配置方式,查看官网相关文档,采用 transform-runtime 这个模块。它会以闭包的方式注入或者间接地帮组件去引入对应的内容。

安装 @babel/plugin-transform-runtime 以及 @babel/runtime(按照官网说法,一个是开发依赖一个是生产依赖)

npm install @babel/plugin-transform-runtime@7.2.0 -D
npm install @babel/runtime@7.2.0

进行相应的配置

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              // {
              //   targets: {
              //     chrome: '67'
              //   },
              //   useBuiltIns: 'usage'
              // }
            ]
          ],
          // 新增配置
          plugins: [
            [
              '@babel/plugin-transform-runtime',
              {
                // 'corejs' 默认值是 false,一般配置为 2,
                // 当配置成 2 需要再安装一个包 @babel/runtime-corejs2,
                // 改为 2 后当页面上不存在的一些 promise 以及 map 方法,
                // 它才会去把这些代码打包到 main.js 里面去。
                'corejs': 2,
                'helpers': true,
                'regenerator': true,
                'useESModules': false
              }
            ]
          ]
        }
      }
    ]
  },
  ...
}

安装 @babel/runtime-corejs2(生产依赖)

npm install @babel/runtime-corejs2@7.2.0

运行打包命令,打包成功,Babel 同样完成了对 es6 代码的处理。

当 Babel 需要配置的内容比较多的时候,继续放在 webpack.config.js 里显然是不合适的,我们可以在项目根路径下创建文件 .babelrc 来进行相关的配置,直接把 webpack.config.js 中 babel-loader 的 options 对应的整个对象移过去即可。

注意的是 .babelrc 这个文件里不能写注释。

去除 webpack.config.js 内 babel-loader 的相关配置项

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  ...
}

在项目根路径下新增 .babelrc 进行 Babel 的相关配置

.babelrc

{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
}

运行打包命令,打包成功。

现在 presets 只配置了一个 preset,当配置了多个 preset,语法转换是有顺序的,执行顺序是从下往上,从右往左。

业务代码的处理 对比 开发类库或第三方模块的处理

在一些低版本浏览器不具备的 es6 的变量或者函数,需要我们通过 Babel 去填充或者说弥补这些内容给低版本的浏览器。

业务代码的处理 对比 开发类库或第三方模块的处理 其实差别只是在于业务代码的处理使用的是 @babel/polyfill 去进行全局变量的注入,而开发类库或第三方模块的处理使用的是 transform-runtime 这个模块,以闭包的形式或者间接地帮组件去引入对应的内容。

.babelrc

当配置项过多时,可以不在 webpack.config.js 里面编写 Babel 的配置项,而是在项目根路径下创建 .babelrc 去进行配置的编写。Babel 会优先采取 .babelrc 的配置项。

七、官方文档

学习完以上内容,可以前往 Webpack 4.0 官网 阅读相关中文文档。

建议阅读的内容为:

  • (1)文档 > 配置,页面左侧的 入口与上下文(entry and context)。
  • (2)文档 > 配置,页面左侧的 输出(output)(了解下即可,不需全部理解)。
  • (3)文档 > 指南,页面左侧的 管理输出。
  • (4)文档 > 配置,页面左侧的 devtool。
  • (5)文档 > 配置,页面左侧的 开发中server(devServer),(要有个大致印象,内容比较多,可以遇到问题再去搜索配置查看文档)。
  • (6)文档 > 指南,页面左侧的 开发环境。
  • (7)文档 > 指南,页面左侧的 模块热替换。
  • (8)文档 > API,页面左侧的 模块热替换。
  • (9)文档 > 概念,页面左侧的 模块热替换(hot module replacement)。

拓展,在命令行运行命令做文件的打包,具体语法参考文档。 例如:入口文件是 index.js,打包输出的文件叫 bundle.js,-o 是 output 的简写。

webpack index.js -o bundle.js
  • (10)文档 > API,页面左侧的 命令行接口。

第四节的实现方法三,也就是对实现一个服务器去监听 Webpack 打包文件改变有兴趣的,可以参考下官方文档:

  • (11)文档 > API,页面左侧的 Node.js API。

英语比较好的小伙伴可以直接阅读 Webpack 4.0 官网 的英文文档。

对 Babel 相关配置有疑惑的可以去网上搜索一下相关文章,以及参考 Babel 官网 内文档介绍。

八、总结

通过以上的学习,我们知道了怎么处理 Webpack 的入口以及输出文件,在打包多个文件时的设置、知道怎么配置 SourceMap 来帮助我们定位代码报错位置、知道怎么监听 Webpack 打包文件的改变来及时更新页面效果、知道怎么开启 HMR,并编写相关代码提高开发效率、知道怎么通过 Babel 把项目中的 es6 代码转换成 es5,在低版本浏览器能正常运行。

当遇到报错问题时,可以先与文中配置项对比是否一致,以及安装的依赖包版本是否一致。

本文最后的代码我会上传到 码云(gitee.com/phao97)上,项目文件夹为 demo03。

如果觉得本篇文章对你有帮助,不妨点个赞或者给相关的 Git 仓库一个 Star,你的鼓励是我持续更新的动力!