初识Webpack | 配置 (万字解析)

 

webpack.jpg

一、Webpack 基础

第一部分,先从简单的“会配置”的要求出发,先了解 Webpack 简单配置以及简单配置会涉及到的面试题。

1. 简单配置

该部分需要掌握:

  1. Webpack 常规配置项有哪些?
  2. 常用 Loader 有哪些?如何配置?
  3. 常用插件(Plugin)有哪些?如何的配置?
  4. Babel 的如何配置?Babel 插件如何使用?

1.1 安装依赖

毫无疑问,先本地安装一下 webpack 以及 webpack-cli

$ npm install webpack webpack-cli -D # 安装到本地依赖
复制代码

安装完成 ✅

+ webpack-cli@4.7.2
+ webpack@5.44.0
复制代码

1.2 工作模式

webpack 在 4 以后就支持 0 配置打包,我们可以测试一下

  1. 新建 ./src/index.js 文件,写一段简单的代码
const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码

此时目录结构

webpack_work                  
├─ src                
│  └─ index.js         
└─ package.json       
复制代码
  1. 直接运行 npx webpack,启动打包

image.png

打包完成,我们看到日志上面有一段提示:The 'mode' option has not been set,...

意思就是,我们没有配置 mode(模式),这里提醒我们配置一下

模式: 供 mode 配置选项,告知 webpack 使用相应模式的内置优化,默认值为 production,另外还有 developmentnone,他们的区别如下

选项 描述
development 开发模式,打包更加快速,省了代码优化步骤
production 生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码
none 不使用任何默认优化选项

怎么配置呢?很简单

  1. 只需在配置对象中提供 mode 选项:
module.exports = {
  mode: 'development',
};
复制代码
  1. 从 CLI 参数中传递:
$ webpack --mode=development
复制代码

1.3 配置文件

虽然有 0 配置打包,但是实际工作中,我们还是需要使用配置文件的方式,来满足不同项目的需求

  1. 根路径下新建一个配置文件 webpack.config.js

  2. 新增基本配置信息

const path = require('path')//nodejs环境下require

module.exports = {
  mode: 'development', // 模式
  entry: './src/index.js', // 打包入口地址
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist') // 输出文件目录,   /dist可加可不加
  }
}

注:__dirname指的是当前文件所在文件夹的绝对路径。

1.4 Loader

这里我们把入口改成 CSS 文件,可能打包结果会如何

1.新增 ./src/main.css

body {
  margin: 0 auto;
  padding: 0 20px;
  max-width: 800px;
  background: #f4f8fb;
}
复制代码
  1. 修改 entry 配置
const path = require('path')

module.exports = {
  mode: 'development', // 模式
  entry: './src/main.css', // 打包入口地址
  output: {
    filename: 'bundle.css', // 输出文件名
    path: path.join(__dirname, 'dist') // 输出文件目录
  }
}
复制代码

3.运行打包命令:npx webpack

这里就报错了!

这是因为:webpack 默认支持处理 JS 与 JSON 文件,其他类型都处理不了,这里必须借助 Loader 来对不同类型的文件的进行处理。

  1. 安装 css-loader 来处理 CSS
npn install css-loader -D
复制代码
  1. 配置资源加载模块
const path = require('path')

module.exports = {
  mode: 'development', // 模式
  entry: './src/main.css', // 打包入口地址
  output: {
    filename: 'bundle.css', // 输出文件名
    path: path.join(__dirname, 'dist') // 输出文件目录
  },
  module: { 
    rules: [ // 转换规则
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: 'css-loader' // use: 对应的 Loader 名称
      }
    ]
  }
}
复制代码
  1. 重新运行打包命令 npx webpack

image.png

 

dist           
└─ bundle.css  # 打包得到的结果
复制代码

image.png

这里这是尝试,入口文件还是需要改回 ./src/index.js

这里我们可以得到一个结论:Loader 就是将 Webpack 不认识的内容转化为认识的内容

1.5 插件(plugin)

与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务

下面来看一个使用的列子:

1.新建 ./src/index.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ITEM</title>
</head>
<body>
  
</body>
</html>
复制代码

如果我想打包后的资源文件,例如:js 或者 css 文件可以自动引入到 Html 中,就需要使用插件 html-webpack-plugin来帮助你完成这个操作

2.本地安装 html-webpack-plugin

npm install html-webpack-plugin -D
复制代码

3.配置插件

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development', // 模式
  entry: './src/index.js', // 打包入口地址
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist') // 输出文件目录
  },
  module: { 
    rules: [
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: 'css-loader' // use: 对应的 Loader 名称
      }
    ]
  },
  plugins:[ // 配置插件
    new HtmlWebpackPlugin({
      template: './src/index.html'//将打包好的文件引入此html模板中 
filename: 'app.html' //模板输出在dist的文件的名字
inject: 'body' //注入位置,默认为head
})
] } 复制代码

运行一下打包,打开 dist 目录下生成的 index.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ITEM</title>
<script defer src="bundle.js"></script></head>
<body>
  
</body>
</html>
复制代码

可以看到它自动的引入了打包好的 bundle.js ,非常方便实用

1.6 自动清空打包目录

每次打包的时候,打包目录都会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空

这里我们可以使用插件 clean-webpack-plugin 来实现

  1. 安装
$ npm install clean-webpack-plugin -D
复制代码
  1. 配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

 

module.exports = {
// ...
plugins:[ // 配置插件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin() // 引入插件
]
}
复制代码

在webpack5中不需要安装插件,在output中配置clean即可:

  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist'), // 输出文件目录
    clean:true
  }

1.7 区分环境

本地开发和部署线上,肯定是有不同的需求

本地环境:

  • 需要更快的构建速度
  • 需要打印 debug 信息
  • 需要 live reload 或 hot reload 功能
  • 需要 sourcemap 方便定位问题
  • ...

生产环境:

  • 需要更小的包体积,代码压缩+tree-shaking
  • 需要进行代码分割
  • 需要压缩图片体积
  • ...

针对不同的需求,首先要做的就是做好环境的区分

方法1:不用安装cross-env

通过--env配置环境变量

package.json

"scripts": {
    "start": "webpack serve -c ./config/webpack.config.js --env development", //-c为指定打包的config文件
    "build": "webpack -c ./config/webpack.config.js --env production"
  }
 
webpack.config.js
 
module.exports = (env) => {
  switch(true) {
    case env.development:
      return merge(commonConfig, developmentConfig)

    case env.production:
      return merge(commonConfig, productionConfig)

    defult:
      return new Error('No matching configuration was found')
  }
}
 

 

方法2:

  1. 本地安装 cross-env [文档地址]
npm install cross-env -D
复制代码
  1. 配置启动命令

打开 ./package.json

"scripts": {
    "dev": "cross-env NODE_ENV=dev webpack serve --mode development", 
    "test": "cross-env NODE_ENV=test webpack --mode production",
    "build": "cross-env NODE_ENV=prod webpack --mode production"
  },
复制代码

npm run dev 相当于
webpack --config build/webpack.config.js
  1. 在 Webpack 配置文件中获取环境变量
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

console.log('process.env.NODE_ENV=', process.env.NODE_ENV) // 打印环境变量

const config = {
  entry: './src/index.js', // 打包入口地址
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist') // 输出文件目录
  },
  module: { 
    rules: [
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: 'css-loader' // use: 对应的 Loader 名称
      }
    ]
  },
  plugins:[ // 配置插件
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}

module.exports = (env, argv) => {
  console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
  // 这里可以通过不同的模式修改 config 配置
  return config;
}
复制代码

4.测试一下看看

  • 执行 npm run build
process.env.NODE_ENV= prod
argv.mode= production
复制代码
  • 执行 npm run test
process.env.NODE_ENV= test
argv.mode= production
复制代码
  • 执行 npm run dev
process.env.NODE_ENV= dev
argv.mode= development
复制代码

这样我们就可以不同的环境来动态修改 Webpack 的配置

1.8 启动 devServer

1.安装 webpack-dev-server

npm install webpack-dev-server -D
复制代码

⚠️注意:当使用的 webpack-dev-server 版本是 3.11.2,用 devServer.contentBase 配置项。,当版本 version >= 4.0.0 时,需要使用 devServer.static 进行配置

2.配置本地服务

// webpack.config.js
const config = {
  // ...
  devServer: {
    //默认配置static:true 静态文件目录默认指向public
    compress: true, //是否启动压缩 gzip
    port: 8081, // 端口号
    open:true  // 是否自动打开浏览器
  },
 // ...
}
module.exports = (env, argv) => {
  console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
  // 这里可以通过不同的模式修改 config 配置
  return config;
}
复制代码

为什么要配置 contentBase ?

因为 webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 static:true(contentBase) 之后,就直接到对应的静态目录(默认为public)下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。

  1. 启动本地服务
$ npm run dev

为了看到效果,我在 html 中添加了一段文字,并在 public 下面放入了一张图片 logo.png

public       
└─ logo.png  
复制代码

接着访问 http://localhost:8080/logo.png

image.png

1.9 引入 CSS

上面,我们在 Loader 里面讲到了使用 css-loader 来处理 css,但是单靠 css-loader 是没有办法将样式加载到页面上。这个时候,我们需要再安装一个 style-loader 来完成这个功能

style-loader 就是将通过css-loader处理好的 css 通过 style 标签的形式添加到页面上

  1. 安装 style-loader [文档地址]
npm install style-loader -D
复制代码
  1. 配置 Loader
const config = {
  // ...
  module: { 
    rules: [
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: ['style-loader','css-loader']
      }
    ]
  },
  // ...
}
复制代码

⚠️注意: Loader 的执行顺序是固定从后往前,即按 css-loader --> style-loader 的顺序执行

  1. 引用样式文件

在入口文件 ./src/index.js 引入样式文件 ./src/main.css

// ./src/index.js

import './main.css';


const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码
/* ./src/main.css */ 
body {
  margin: 10px auto;
  background: cyan;
  max-width: 800px;
}
复制代码
  1. 重启一下本地服务,访问 http://localhost:8080/

image.png

css-loader(让webpack识别css文件) --> style-loader(将css文件转为style标签样式并插入head)

style-loader 核心逻辑相当于:

const content = `${样式内容}`
const style = document.createElement('style');
style.innerHTML = content;
document.head.appendChild(style);
复制代码

通过动态添加 style 标签的方式,将样式引入页面

1.10 CSS 兼容性

使用 postcss-loader,自动添加 CSS3 部分属性的浏览器前缀

上面我们用到的 transform: translateX(-50%);,需要加上不同的浏览器前缀,这个我们可以使用 postcss-loader 来帮助我们完成

⚠️ 这里有个很大的坑点:参考文档配置好后,运行的时候会报错

😁

npm install postcss postcss-loader postcss-preset-env -D
复制代码

添加 postcss-loader 加载器

const config = {
  // ...
  module: { 
    rules: [
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: [
          'style-loader',
          'css-loader', 
          'postcss-loader'
        ]
      }
    ]
  }, 
  // ...
}
复制代码

创建 postcss 配置文件 postcss.config.js

// postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')]
}
复制代码

创建 postcss-preset-env 配置文件 .browserslistrc

# 换行相当于 and
last 2 versions # 回退两个浏览器版本
> 0.5% # 全球超过0.5%人使用的浏览器,可以通过 caniuse.com 查看不同浏览器不同版本占有率
IE 10 # 兼容IE 10

再尝试运行一下

image.png

前缀自动加上了 👏

1.11 引入 Less 或者 Sass

less 和 sass 同样是 Webpack 无法识别的,需要使用对应的 Loader 来处理一下

文件类型 loader
Less less-loader
Sass sass-loader node-sass 或 dart-sass

Less 处理相对比较简单,直接添加对应的 Loader 就好了

Sass 不光需要安装 sass-loader 还得搭配一个 node-sass,这里 node-sass 建议用淘宝镜像来安装,npm 安装成功的概率太小了 🤣

这里我们就使用 Sass 来做案例

  1. 安装
$ npm install sass-loader -D
# 淘宝镜像
$ npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
复制代码
  1. 新建 ./src/sass.scss

Sass 文件的后缀可以是 .scss(常用) 或者 .sass

$color: rgb(190, 23, 168);

body {
  p {
    background-color: $color;
    width: 300px;
    height: 300px;
    display: block;
    text-align: center;
    line-height: 300px;
  }
}
复制代码
  1. 引入 Sass 文件
import './main.css';
import './sass.scss' // 引入 Sass 文件


const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码
  1. 修改配置
const config = {
   // ...
   rules: [
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader', 
        ]
      },
    ]
  },
  // ...
}
复制代码

来看一下执行结果

image.png

将sass样式转换成css样式

1.12 分离样式文件

前面,我们都是依赖 style-loader 将样式通过 style 标签的形式添加到页面上

但是,更多时候,我们都希望可以通过 CSS 文件的形式<link href=...>引入到页面上

  1. 安装 mini-css-extract-plugin
$ npm install mini-css-extract-plugin -D
复制代码
  1. 修改 webpack.config.js 配置
// ...
// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')


const config = {
  // ...
  module: { 
    rules: [
      // ...
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          // 'style-loader',
          MiniCssExtractPlugin.loader, // 添加 loader
          'css-loader',
          'postcss-loader',
          'sass-loader', 
        ] 
      },
    ]
  },
  // ...
  plugins:[ // 配置插件
    // ...
    new MiniCssExtractPlugin({ // 添加插件
      filename: '[name].[hash:8].css' //指定打包的css文件名 可styles/[name].[hash:8].css  
    }),
    // ...
  ]
}

// ...
复制代码
  1. 查看打包结果
dist                    
├─ avatar.d4d42d52.png  
├─ bundle.js            
├─ index.html           
├─ logo.56482c77.png    
└─ main.3bcbae64.css # 生成的样式文件  
复制代码

image.png

官方文档:

推荐 production 环境的构建将 CSS 从你的 bundle 中分离出来,这样可以使用 CSS/JS 文件的并行加载。 这可以通过使用 mini-css-extract-plugin 来实现,因为它可以创建单独的 CSS 文件。 对于 development 模式(包括 webpack-dev-server),你可以使用 style-loader,因为它可以使用多个 标签将 CSS 插入到 DOM 中,并且反应会更快。

i 不要同时使用 style-loader 与 mini-css-extract-plugin

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== "production";

module.exports = {
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          devMode ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },
  plugins: [].concat(devMode ? [] : [new MiniCssExtractPlugin()]),
};

1.13 图片和字体文件

虽然上面在配置开发环境的时候,我们可以通过设置 contentBase 去直接读取图片类的静态文件,看一下下面这两种图片使用情况

  1. 页面直接引入
<!-- 本地可以访问,生产环境会找不到图片 -->
<img src="/logo.png" alt="">

11.jpg:1 Failed to load resource: the server responded with a status of 404 (Not Found)

  1. 背景图引入
<div id="imgBox"></div>
复制代码
/* ./src/main.js*/
...import logo from '../public/11.jpg'
复制代码

直接会报错

image.png

所以实际上,Webpack 无法识别图片文件,需要在打包的时候处理一下

Webpack4中常用的处理图片文件的 Loader 包含:

Loader 说明
file-loader 解决图片引入问题,并将图片 copy 到指定目录,默认为 dist
url-loader 解依赖 file-loader,当图片或字体小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝
img-loader 压缩图片
  1. 安装 file-loader
npm install file-loader -D
复制代码
  1. 修改配置
const config = {
  //...
  module: { 
    rules: [
      {
         // ...
      }, 
      {
        test: /\.(jpe?g|png|gif)$/i, // 匹配图片文件
        use:[
          'file-loader' // 使用 file-loader
        ]
      }
    ]
  },
  // ...
}
复制代码

3.引入图片

<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
...
<body>
  <p></p>
  <div id="imgBox"></div>
</body>
</html>
复制代码

样式文件中引入

/* ./src/sass.scss */

$color: rgb(190, 23, 168);

body {
  p {
    width: 300px;
    height: 300px;
    display: block;
    text-align: center;
    line-height: 300px;
    background: url('../public/logo.png');
    background-size: contain;
  }
}

我们可以看到图片文件的名字都已经变了,并且带上了 hash 值,然后我看一下打包目录

dist                                     
├─ 56482c77280b3c4ad2f083b727dfcbf9.png  
├─ bundle.js                             
├─ d4d42d529da4b5120ac85878f6f69694.png  
└─ index.html                            
复制代码

dist 目录下面多了两个文件,这正是 file-loader 拷贝过来的

  1. 安装 url-loader
$ npm install url-loader -D
复制代码
  1. 配置 url-loader

配置和 file-loader 类似,多了一个 limit 的配置

const config = {
  //...
  module: { 
    rules: [
      {
         // ...
      }, 
      {
        test: /\.(jpe?g|png|gif)$/i,
        use:[
          {
            loader: 'url-loader',
            options: {
              name: '[name][hash:8].[ext]',
              // 文件小于 50k 会转换为 base64,大于则拷贝文件
              limit: 50 * 1024
            }
          }
        ]
      },
    ]
  },
  // ...
}
复制代码

看一下,我们两个图片文件的体积

public         
├─ avatar.png # 167kb
└─ logo.png   # 43kb 
复制代码

我们打包看一下效果

image.png

很明显可以看到 logo.png 文件已经转为 base64 ,而大于50kb的则直接拷贝到dist文件下

参考文档进行配置即可

https://v4.webpack.docschina.org/loaders/file-loader

1.14 资源模块的使用

在 webpack 5 之前,通常使用:

  • raw-loader 将文件导入为字符串
  • url-loader 将文件作为 data URI 内联到 bundle 中,若小于限定的则转为base64内联到bundle,否则拷贝到dist
  • file-loader 将文件发送到输出目录

webpack5 新增资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外的 loader。

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  1. asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能,可在dist中找到56482c77280b3c4ad2f083b727dfcbf9.png.
  2. asset/inline 将资源导出为 dataUrl (base64)的形式,类似之前的 url-loader 的小于 limit 参数时功能,在dist文件内找不到.
  3. asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能.在dist中找不到
  4. asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource

// ./src/index.js


import logo from '../public/avatar.png'

const img = new Image()
img.src = logo

document.getElementById('imgBox').appendChild(img)

根据类型的不同,img.src不同,上面的为inline(打包为base64),下面为resource(在dist中存在)
image.png

注:output{assetModuleFilename:"[name][hash:8][ext]"}跟generator{filename}功能一样,但后者的优先级更高

文档地址:https://webpack.docschina.org/guides/asset-modules

贴一下修改后的完整代码

// ./src/index.js

const config = {  
//...
output{assetModuleFilename:"[name][hash:8][ext]"}// ... module: { rules: [ // ... { test: /\.(jpe?g|png|gif)$/i, type: 'asset', //asset/inline generator: { // 输出文件位置以及文件名 // [ext]为扩展名,自动补全 自带 "." 这个与 url-loader 配置不同 filename: "[name][hash:8][ext]" }, parser: { dataUrlCondition: { maxSize: 50 * 1024 //超过50kb不转 base64 } } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, type: 'asset', generator: { // 输出文件位置以及文件名 filename: "[name][hash:8][ext]"//如果是/fonts[hash:8][ext]则打包到dist并生成fonts }, parser: { //定义解析器 dataUrlCondition: { maxSize: 10 * 1024 // 超过100kb不转 base64 } } }, ] }, // ... } module.exports = (env, argv) => { console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值 // 这里可以通过不同的模式修改 config 配置 return config; } 复制代码

执行打包,结果和之前一样

1.15 JS 兼容性(Babel)

在开发中我们想使用最新的 Js 特性,但是有些新特性的浏览器支持并不是很好,所以 Js 也需要做兼容处理,常见的就是将 ES6 语法转化为 ES5。

这里将登场的“全场最靓的仔” -- Babel

  1. 未配置 Babel

我们写点 ES6 的东西

// ./src/index.js

import './main.css';
import './sass.scss'
import logo from '../public/avatar.png'

import './fonts/iconfont.css'

// ...

class Author {
  name = 'ITEM'
  age = 18
  email = 'lxp_work@163.com'

  info =  () => {
    return {
      name: this.name,
      age: this.age,
      email: this.email
    }
  }
}


module.exports = Author

复制代码

为了方便看源码,我们把 mode 换成 development

接着执行打包命令

打包完成之后,打开 bundle.js 查看打包后的结果

 

image.png

虽然我们可以找到我们的代码,但是阅读起来比较不直观,我们先设置 mode 为 none,以最原始的形式打包,再看一下打包结果

image.png

打包后的代码变化不大,只是对图片地址做了替换,还是ES6的语法,部分旧版本浏览器不兼容,接下来看看配置 babel 后的打包结果会有什么变化

  1. 安装依赖
$ npm install babel-loader @babel/core @babel/preset-env -D
复制代码
  • babel-loader 使用 Babel 加载 ES2015+ 代码并将其转换为 ES5
  • @babel/core Babel 编译的核心包
  • @babel/preset-env Babel 编译的预设,可以理解为 一组Babel 插件的集合
  1. 配置 Babel 预设
// webpack.config.js
// ...
const config = {
  entry: './src/index.js', // 打包入口地址
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist'), // 输出文件目录
  },
  module: { 
    rules: [
      {
        test: /\.js$/i,
exclude:/node_modules/, //node_modules里的js不需要转换成ES5 use: [ { loader: 'babel-loader', options: { presets: [//预设 '@babel/preset-env' ], } } ] }, // ... ] }, //... } // ... 复制代码

配置完成之后执行一下打包

image.png

刚才写的 ES6 class 写法 已经转换为了 ES5 的构造函数形式

尽然是做兼容处理,我们自然也可以指定到底要兼容哪些浏览器

为了避免 webpack.config.js 太臃肿,建议将 Babel 配置文件提取出来

根目录下新增 .babelrc.js

// ./babelrc.js

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        // useBuiltIns: false 默认值,无视浏览器兼容配置,引入所有 polyfill
        // useBuiltIns: entry 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill
        // useBuiltIns: usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加
        useBuiltIns: "entry",
        corejs: "3.9.1", // 是 core-js 版本号
        targets: {
          chrome: "58",
          ie: "11",
        },
      },
    ],
  ],
};

复制代码

好了,这里一个简单的 Babel 预设就配置完了

  1. 配置 Babel 插件

对于正在提案中,还未进入 ECMA 规范中的新特性,Babel 是无法进行处理的,必须要安装对应的插件,例如:async、await, 现在进入ECMA规范了。。。

// 新增装饰器的使用 @log('hi') class MyClass { }

Babel 提供了对应的插件:

  • @babel/plugin-proposal-decorators
  • @babel/plugin-proposal-class-properties

安装一下:

$ npm install babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D复制代码

打开 .babelrc.js 加上插件的配置

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "entry",
        corejs: "3.9.1",
        targets: {
          chrome: "58",
          ie: "11",
        },
      },
    ],
  ],
  plugins: [    
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    ["@babel/plugin-proposal-class-properties", { loose: true }],
  ]
};
复制代码

这样就可以打包了,在 bundle.js 中已经转化为浏览器支持的 Js 代码

1. 16 代码分离与动态导入

运行时优化的核心就是提升首屏的加载速度,主要的方式就是

  • 降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载
  • 把多个模块共享的代码抽离出去,减少入口文件的大小
1.0 代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的

bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的

bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

常用的代码分离方法有三种:

入口起点:使用 entry 配置手动地分离代码。

防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离

chunk。

动态导入:通过模块的内联函数调用来分离代码。

1.1 入口点分割

entry: {

index: './src/index.js',

another: './src/another-module.js',

},

output: {

filename: '[name].bundle.js'

},

}`
```

这种方式的确存在一些隐患:

  • 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个bundle 中。
  • 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在

./src/index.js 中也引入过 lodash ,这样就在两个 bundle 中造成重复引用。

1.2 splitChunks 分包配置

optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的

 

SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk

中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重

复的 lodash 模块去除:

//...

optimization: {

//...

splitChunks: {

chunks: 'all',

}

},

}

 

1.3 代码懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把

你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用

或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体

体积,因为某些代码块可能永远不会被加载

 

针对首屏加载不太需要的一些资源,我们可以通过懒加载的方式去实现,下面看一个小🌰

  • 需求:点击图片给图片加一个描述

1. 新建图片描述信息

desc.js

const ele = document.createElement('div')
ele.innerHTML = '我是图片描述'
module.exports = ele
复制代码

2. 点击图片引入描述

index.js

import './main.css';
import './sass.scss'
import logo from '../public/avatar.png'

import '@/fonts/iconfont.css'

const a = 'Hello ITEM'
console.log(a)

const img = new Image()
img.src = logo

document.getElementById('imgBox').appendChild(img)

// 按需加载
img.addEventListener('click', () => {
  import(/* webpackChunkName: '685' */  './desc').then(({ default: element }) => {
    console.log(element)
    document.body.appendChild(element)
  })
})

这里有句注释,我们把它称为 webpack 魔法注释: webpackChunkName: 'math' ,

告诉webpack打包生成的文件名为685。

 

3. 查看效果

  • 点击前

image.png

image.png

  • 点击后

image.png

image.png

1.4 prefetch 与 preload

上面我们使用异步加载的方式引入图片的描述,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取

prefetch(预获取):将来某些导航下可能需要的资源

preload(预加载):当前导航下可能需要资源

1.4.1 prefetch
  • prefetch (预获取):浏览器空闲的时候进行资源的拉取

改造一下上面的代码

// 按需加载
img.addEventListener('click', () => {
  import( /* webpackPrefetch: true */ './math').then(({ default: element }) => {
    console.log(element)
    document.body.appendChild(element)
  })
})
复制代码

添加第二句魔法注释: webpackPrefetch: true

告诉 webpack 执行预获取。这会生成 <link rel="prefetch" href="math.js">

并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件.

运行打包后:

我们发现,在还没有点击按钮时, math.bundle.js 就已经下载下来了。同时,在

app.html 里webpack自动添加了一句:<link rel="prefetch" href="math.js">

 

1.4.2 preload
  • preload (预加载):提前加载后面会用到的关键资源
  • ⚠️ 因为会提前拉取资源,如果不是特殊需要,谨慎使用

官网示例:

import(/* webpackPreload: true */ 'ChartingLibrary');


与 prefetch 指令相比,preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。

1.17 CSS压缩

css-minimizer-webpack-plugin

$ npm install css-minimizer-webpack-plugin --save-dev

接着在 webpack 配置中加入该插件。示例:

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

1.18 缓存

1.18.1输出文件的文件名

如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。

可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,其中, [contenthash] substitution 将根据资源内容创建出唯一 hash。

1.18.2 缓存第三方库

将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较

推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。

配置输出文件名  
output: {//出口配置
    filename: 'scripts/[name].[contenthash].js',//文件名,随机生成hash,如index.ashdoadosd.js,每次打包文件名不同,刷新缓存

  },

第三方库

  splitChunks: {//代码分离
      cacheGroups: {//缓存
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',//对于modules很少修改的模块,名字固定即可
          chunks: 'all'
        }
      }
    }

1.19 拆分开发环境和生产环境配置

1.19.1公共路径

publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所 有资源的基础路径。

 output: {

.../
      publicPath: 'http://localhost:8080/'
    }
//在打包好的html文件中标签为<link href='http://localhost:8080/styles/46sdfsw9wf13w.css'>

1.19.2环境变量

webpack 命令行 环境配置--env 参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js 中可以访问到这些环境变量。例如,--env production--env goal=local

npx webpack --env goal=local --env production --progress
复制代码

对于我们的 webpack 配置,有一个必须要修改之处。通常,module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数:

module.exports = (env) => {
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}

注:terser-webpack-plugin为webpack5开箱即用的插件,用来压缩js,但在前面optimization中配置了CSS压缩后,要重新引入terser并在optimization中配置才可压缩js代码。

先上总体代码webpack.config.js:

const path = require('path')//路径包
const HtmlWebpackPlugin = require('html-webpack-plugin')//打包后的文件自动引入html模板重
const MiniCssExtractPlugin = require('mini-css-extract-plugin')//分离样式文件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')//CSS压缩
const TerserPlugin = require('terser-webpack-plugin')//js压缩

const toml = require('toml')//数据处理
const yaml = require('yaml')//数据处理
const json5 = require('json5')//处理json5数据

module.exports = {
  entry: {//入口配置,多点打包入口
    index: './src/index.js',
    another: './src/another-module.js'
  },

  output: {//出口配置
    filename: 'scripts/[name].[contenthash].js',//文件名,随机生成hash,如index.ashdoadosd.js,每次打包文件名不同,刷新缓存
    path: path.resolve(__dirname, './dist'),//输出路径,__dirname为当前目录下的绝对路径
    clean: true,//自动清理dist文件,webpack4中是用插件clean-webpack-plugin
    assetModuleFilename: 'images/[contenthash][ext]',//资源管理模块的输出文件名
    publicPath: 'http://localhost:8080/'
  },

  mode: env.production ? 'production' : 'development',//开发模式

  devtool: 'inline-source-map',//sourcemap

  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html',//插入的html模板
      filename: 'app.html',//输出到dist的html文件名
      inject: 'body'//插入位置
    }),

    new MiniCssExtractPlugin({
      filename: 'styles/[contenthash].css'//分离样式文件输出的名字
    })
  ],

  devServer: {//默认配置 port:8080   open:true compress:true
    static: './dist'//静态文件的处理输出的路径
  },

  module: {
    rules: [
      {
        test: /\.png$/,
        type: 'asset/resource',//资源管理模块,直接打包到dist
        generator: {
          filename: 'images/[contenthash][ext]'
        }
      },

      {
        test: /\.svg$/,//图标
        type: 'asset/inline'//base64格式
      },

      {
        test: /\.txt$/,
        type: 'asset/source'//打包源码
      },

      {
        test: /\.jpg$/,
        type: 'asset',//动态匹配inline或resource
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024 * 1024//限制大小
          }
        }
      },

      {
        test: /\.(css|less)$/,//处理less->css->样式分离引入
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
      },

      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,//字体
        type: 'asset/resource'
      },

      {
        test: /\.(csv|tsv)$/,
        use: 'csv-loader'
      },

      {
        test: /\.xml$/,
        use: 'xml-loader'
      },

      {
        test: /\.toml$/,
        type: 'json',
        parser: {
          parse: toml.parse
        }
      },

      {
        test: /\.yaml$/,
        type: 'json',
        parser: {
          parse: yaml.parse
        }
      },

      {
        test: /\.json5$/,
        type: 'json',
        parser: {
          parse: json5.parse
        }
      },

      {
        test: /\.js$/,
        exclude: /node_modules/,//对modules中的js文件不转化
        use: {
          loader: 'babel-loader',//ES6+转ES5
          options: {
            presets: ['@babel/preset-env'],//预处理
            plugins: [//插件,主要用来对未纳入ECM规范的语法进行转译
              [
                '@babel/plugin-transform-runtime'
              ]
            ]
          }
        }
      }
    ]
  },

  optimization: {//优化
    minimizer: [
      new CssMinimizerPlugin(),//CSS压缩
      new TerserPlugin()//js压缩
    ],

    splitChunks: {//代码分离
      cacheGroups: {//缓存
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',//对于modules很少修改的模块,名字固定即可
          chunks: 'all'
        }
      }
    }
  }
}

2.0 拆分文件与合并

目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如webpack.config.dev.js(开发环境配置)和 webpack.config.prod.js(生产环境配置)。在项目根目录下创建一个配置文件夹 config 来存放他们。

配置 npm 脚本来简化命令行的输入,这时可以省略 npx

package.json

{
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
}
}

注:-c是指定要打包使用的config文件

创建 webpack.config.common.js,配置公共的内容:

webpack.config.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')

module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},

output: {
// 注意这个dist的路径设置成上一级
path: path.resolve(__dirname, '../dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext]',
},

plugins: [
new HtmlWebpackPlugin({
 template: './index.html',
 filename: 'app.html',
 inject: 'body'
}),

new MiniCssExtractPlugin({
 filename: 'styles/[contenthash].css'
})
],

module: {
rules: [
 {
   test: /\.png$/,
   type: 'asset/resource',
   generator: {
     filename: 'images/[contenthash][ext]'
   }
 },

 {
   test: /\.svg$/,
   type: 'asset/inline'
 },

 {
   test: /\.txt$/,
   type: 'asset/source'
 },

 {
   test: /\.jpg$/,
   type: 'asset',
   parser: {
     dataUrlCondition: {
       maxSize: 4 * 1024
     }
   }
 },

 {
   test: /\.(css|less)$/,
   use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
 },

 {
   test: /\.(woff|woff2|eot|ttf|otf)$/,
   type: 'asset/resource'
 },

 {
   test: /\.(csv|tsv)$/,
   use: 'csv-loader'
 },

 {
   test: /\.xml$/,
   use: 'xml-loader'
 },

 {
   test: /\.toml$/,
   type: 'json',
   parser: {
     parse: toml.parse
   }
 },

 {
   test: /\.yaml$/,
   type: 'json',
   parser: {
     parse: yaml.parse
   }
 },

 {
   test: /\.json5$/,
   type: 'json',
   parser: {
     parse: json5.parse
   }
 },

 {
   test: /\.js$/,
   exclude: /node_modules/,
   use: {
     loader: 'babel-loader',
     options: {
       presets: ['@babel/preset-env'],
       plugins: [
         [
           '@babel/plugin-transform-runtime'
         ]
       ]
     }
   }
 }
]
},

optimization: {
splitChunks: {
 cacheGroups: {
   vendor: {
     test: /[\\/]node_modules[\\/]/,
     name: 'vendors',
     chunks: 'all'
   }
 }
}
},

//关闭 webpack 的性能提示
performance: {
hints:false
}
}
复制代码

webpack.config.dev.js

module.exports = {
// 开发环境不需要配置缓存
output: {
filename: 'scripts/[name].js',
},

// 开发模式
mode: 'development',

// 配置 source-map
devtool: 'inline-source-map',

// 本地服务配置
devServer: {
static: './dist'
}
}

webpack.config.prod.js

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
// 生产环境需要缓存
output: {
filename: 'scripts/[name].[contenthash].js',
publicPath: 'http://localhost:8080/'
},

// 生产环境模式
mode: 'production',

// 生产环境 css 压缩
optimization: {
minimizer: [
 new CssMinimizerPlugin()
]
}
}

如何保证配置合并没用问题呢?webpack-merge 这个工具可以完美解决这个问题。

安装 webpack-merge:

npm install webpack-merge -D

创建 webpack.config.js,合并代码:

const { merge } = require('webpack-merge')

const commonConfig = require('./webpack.config.common.js')

const productionConfig = require('./webpack.config.prod.js')

const developmentConfig = require('./webpack.config.dev')

module.exports = (env) => {
switch(true) {
case env.development:
 return merge(commonConfig, developmentConfig)
case env.production:
 return merge(commonConfig, productionConfig)
default:
 throw new Error('No matching configuration was found!');
}
}

建议先看完视频再看第二篇文章,更好理解

参考:

B站千锋陆荣涛最新前端webpack5全套教程

构建 webpack5.x 知识体系

阅读剩余
THE END