# 简单配置

该部分需要掌握:

Webpack 常规配置项有哪些?
常用 Loader 有哪些?如何配置?
常用插件(Plugin)有哪些?如何的配置?
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 配置打包,我们可以测试一下 新建 ./src/index.js 文件,写一段简单的代码

const a = 'Hello ITEM'
console.log(a)
module.exports = a;

此时目录结构 webpack_work
├─ src
│ └─ index.js
└─ package.json
直接运行 npx webpack,启动打包 打包完成,我们看到日志上面有一段提示:The 'mode' option has not been set,...

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

只需在配置对象中提供 mode 选项:

module.exports = {
  mode: 'development',
};

从 CLI 参数中传递:

$ webpack --mode=development

# 1.3 自动清空打包目录

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

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

安装

$ npm install clean-webpack-plugin -D

配置

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() // 引入插件
  ]
}

# 1.4 区分环境

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

  • 本地环境:

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

  • 生产环境:

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

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

  1. 本地安装 cross-env [https://www.npmjs.com/package/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"
  },
  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;
}

# 1.5 启动 devServer

  1. 安装 webpack-dev-server [https://webpack.docschina.org/configuration/dev-server/#devserver]
npm intall webpack-dev-server@3.11.2 -D

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

  1. 配置本地服务
// webpack.config.js
const config = {
  // ...
  devServer: {
    contentBase: path.resolve(__dirname, 'public'), // 静态文件目录
    compress: true, //是否启动压缩 gzip
    port: 8080, // 端口号
    // open:true  // 是否自动打开浏览器
  },
 // ...
}
module.exports = (env, argv) => {
  console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
  // 这里可以通过不同的模式修改 config 配置
  return config;
}

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

webpack-dev-server v4.0.0+ 要求 node >= v12.13.0、webpack >= v4.37.0(但是我们推荐使用 webpack >= v5.0.0)和 webpack-cli >= v4.7.0。

  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true, //是否启动压缩 gzip
    port: 9000,
    open: true, // 是否自动打开浏览器
  },

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

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

<!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>
  <p>ITEM</p>
</body>
</html>

public       
└─ logo.png  

打开地址 http://localhost:8080/

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

# 1,6 引入 CSS

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

安装 style-loader [文档地址]

npm install style-loader -D

配置 Loader

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

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

引用样式文件

在入口文件 ./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;
}

重启一下本地服务,访问 http://localhost:8080/

这样样式就起作用了,继续修改一下样式

body {
  margin: 10px auto;
  background: cyan;
  max-width: 800px;
  /* 新增 */
  font-size: 46px;
  font-weight: 600;
  color: white;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
}

保存之后,样式就自动修改完成了

style-loader 核心逻辑相当于:

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

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

# 1.7 CSS 兼容性

使用 postcss-loader (opens new window),自动添加 CSS3 部分属性的浏览器前缀 上面我们用到的 transform: translateX(-50%);,需要加上不同的浏览器前缀,这个我们可以使用 postcss-loader 来帮助我们完成

  1. 安装
npm install postcss postcss-loader postcss-preset-env -D
  1. 添加 postcss-loader 加载器
const config = {
  // ...
  module: { 
    rules: [
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: [
          'style-loader',
          'css-loader', 
          'postcss-loader'
        ]
      }
    ]
  }, 
  // ...
}

  1. 创建 postcss 配置文件 postcss.config.js
// postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')]
}

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

# 1.8 引入 Less 或者 Sass

less 和 sass 同样是 Webpack 无法识别的,需要使用对应的 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', 
        ]
      },
    ]
  },
  // ...
}

来看一下执行结果

# 1.9 分离样式文件

前面,我们都是依赖 style-loader 将样式通过 style 标签的形式添加到页面上 但是,更多时候,我们都希望可以通过 CSS 文件的形式引入到页面上

  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'
    }),
    // ...
  ]
}

// ...

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

# 1.10 图片文件

常用的处理图片文件的 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
        ]
      }
    ]
  },
  // ...
}

  1. 引入图片
<!-- ./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;
  }
}


js 文件中引入

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

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

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

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

启动服务,我们看一下效果

显示正常 ✌️

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

dist                                     
├─ 56482c77280b3c4ad2f083b727dfcbf9.png  
├─ bundle.js                             
├─ d4d42d529da4b5120ac85878f6f69694.png  
└─ index.html                            

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

如果想要修改一下名称,可以加个配置

const config = {
  //...
  module: { 
    rules: [
      {
         // ...
      }, 
      {
        test: /\.(jpe?g|png|gif)$/i,
        use:[
          {
            loader: 'file-loader',
            options: {
              name: '[name][hash:8].[ext]'
            }
          }
        ]
      }
    ]
  },
  // ...
}

再次打包看一下

dist                   
├─ avatard4d42d52.png  
├─ bundle.js           
├─ index.html          
└─ logo56482c77.png    
  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 

我们打包看一下效果

很明显可以看到 logo.png 文件已经转为 base64 了👌

# 1.11 字体文件

  1. 首先,从 iconfont.cn (opens new window) 下载字体文件到本地

  2. 在项目中,新建 ./src/fonts 文件夹来存放字体文件 然后,引入到入口文件

// ./src/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)

  1. 在 ./src/index.html 中使用
<!DOCTYPE html>
<html lang="en">
...
<body>
  <p></p>
  <!-- 使用字体图标文件 -->
  <!-- 1)iconfont 对应 font-family 设置的值-->
  <!-- 2)icon-member 图标 class 名称可以在 iconfont.cn 中查找-->
  <i class="iconfont icon-member"></i>
  <div id="imgBox"></div>
</body>
</html>

  1. 增加字体文件的配置
const config = {
  // ...
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,  // 匹配字体文件
    use: [
      {
        loader: 'url-loader',
        options: {
          name: 'fonts/[name][hash:8].[ext]', // 体积大于 10KB 打包到 fonts 目录下 
          limit: 10 * 1024,
        } 
      }
    ]
  },
  // ...
}

打包一下,看看效果

webpack5,内置了资源处理模块,file-loader 和 url-loader 都可以不用安装

# 1.12 资源模块的使用

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

资源模块支持以下四个配置:

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

贴一下修改后的完整代码

// ./src/index.js

const config = {
  // ...
  module: { 
    rules: [
      // ... 
      {
        test: /\.(jpe?g|png|gif)$/i,
        type: 'asset',
        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]"
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 超过100kb不转 base64
          }
        }
      },
    ]
  },
  // ...
}

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

# 1.13 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 查看打包后的结果

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

打包后的代码变化不大,只是对图片地址做了替换,接下来看看配置 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,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env'
              ],
            }
          }
        ]
      },
    // ...
    ]
  },
  //...
}
// ...

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

刚才写的 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 预设就配置完了

常见 Babel 预设还有: @babel/preset-flow @babel/preset-react @babel/preset-typescript

  1. 配置 Babel 插件 对于正在提案中,还未进入 ECMA 规范中的新特性,Babel 是无法进行处理的,必须要安装对应的插件,例如:
// ./ 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)

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

function log(text) {
  return function(target) {
    target.prototype.logger = () => `${text}${target.name}`
  }
}

const test = new MyClass()
test.logger()