拆分配置文件

将 webpack.config.js 按实际需要进行拆分,分成公用部分和特定用途部分

核心内容:

  1. 按照用途,将原配置文件拆分为多个配置文件
  2. 通过 webpack-merge 合并配置
  3. 运行 webpack 时使用指定配置文件进行打包

步骤大纲

  1. 安装 webpack-merge
  2. build/webpack.config.js 拆分成:公用配置、开发配置、生成配置
  3. 为快捷命令指定相应的 webpack 配置文件

具体步骤

1、安装 webpack-merge

  1. npm i webpack-merge -D

2、将 build目录下的 webpack.config.js 拆分为以下3个文件

  • 公用配置:webpack.config.default.js
  • 开发配置:webpack.config.dev.js
  • 生产配置:webpack.config.prod.js

webpack.config.default.js

  1. const path = require('path')
  2. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  3. const HtmlWebpackPlugin = require('html-webpack-plugin')
  4. const MiniCssExtractPlugin = require('mini-css-extract-plugin')
  5. const { VueLoaderPlugin } = require('vue-loader')
  6. module.exports = {
  7. // 入口文件
  8. entry: './src/main.js',
  9. // 打包输出
  10. output: {
  11. path: path.join(__dirname, '../dist'),
  12. filename: 'app.bundle.js'
  13. },
  14. module: {
  15. rules: [
  16. // 处理纯 css 样式文件
  17. {
  18. test: /\.css$/i,
  19. use: [MiniCssExtractPlugin.loader, 'css-loader']
  20. },
  21. // 处理 sass|scss 样式文件
  22. {
  23. test: /\.(scss|sass)$/i,
  24. use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
  25. },
  26. // 处理图片和字体文件
  27. {
  28. test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
  29. type: "asset"
  30. },
  31. // 处理 ES6+ 语法
  32. {
  33. test: /\.m?js$/i,
  34. exclude: /(node_modules|bower_components)/,
  35. use: {
  36. loader: 'babel-loader',
  37. options: {
  38. presets: ['@babel/preset-env'],
  39. plugins: ['@babel/plugin-transform-runtime']
  40. }
  41. }
  42. },
  43. {
  44. test: /\.vue$/i,
  45. use: 'vue-loader'
  46. }
  47. ]
  48. },
  49. plugins: [
  50. // 生成 index.html
  51. new HtmlWebpackPlugin({
  52. template: './public/index.html'
  53. }),
  54. // 抽取样式
  55. new MiniCssExtractPlugin(),
  56. // 打包清理
  57. new CleanWebpackPlugin(),
  58. // 处理 Vue 组件
  59. new VueLoaderPlugin()
  60. ],
  61. }

webpack.config.dev.js

  1. const path = require('path')
  2. const { merge } = require('webpack-merge')
  3. const defaultConfig = require('./webpack.config.default')
  4. module.exports = merge(defaultConfig, {
  5. // 开发模式
  6. mode: 'development',
  7. // 开发服务器
  8. devServer: {
  9. // host 公共wifi下可以访问本地项目
  10. host:'0.0.0.0',
  11. static: {
  12. directory: path.join(__dirname, '../dist')
  13. },
  14. port: 9000
  15. }
  16. })

webpack.config.prod.js

  1. const { merge } = require('webpack-merge')
  2. const defaultConfig = require('./webpack.config.default')
  3. module.exports = merge(defaultConfig, {
  4. // 生产模式(打包后的文件会作压缩处理)
  5. mode: 'production',
  6. // 生成更高质量的 Source Map 文件
  7. devtool: 'source-map',
  8. output: {
  9. // 生成的文件带 hash 版本信息
  10. filename: 'app.[fullhash].js'
  11. }
  12. })

3、为 package.json 中的快捷命令指定对应的 webpack 配置文件

  1. {
  2. "scripts": {
  3. "serve": "webpack serve -c ./build/webpack.config.dev.js --open",
  4. "build": "webpack -c ./build/webpack.config.prod.js"
  5. },
  6. // ...
  7. }

解决跨域问题

解决开发时,因调用不同源的 API 接口而产生的跨域问题

核心内容:

  1. 在项目中请求非同源的后端接口
  2. 通过 webpack-dev-serverdevServer 配置项启用代理服务,解决跨域问题

步骤大纲

  1. 安装 axios
  2. 在任意页面发起请求(URL:https://www.uinav.com/api/public/v1/home/swiperdata)
  3. 配置 devServer.proxy 代理
  4. 修改请求 URL 路径为相对路径

具体步骤

1、安装网络请求库 axios

  1. npm i axios -D

2、在 src/views/Home/index.vue中发起网络请求

  1. async created() {
  2. const url = 'https://www.uinav.com/api/public/v1/home/swiperdata'
  3. const result = await axios.get(url)
  4. console.log(result)
  5. }

当前该请求不会成功,会报跨域错误:

image.png

3、在开发环境的配置文件 build/webpack.config.dev.js中配置代理服务

  1. // ...
  2. module.exports = merge(defaultConfig, {
  3. // ...
  4. devServer: {
  5. // ...
  6. // 配置代理
  7. proxy: {
  8. //配置多个代理,
  9. {
  10. '/api1': {
  11. target: 'https://www.uinav1.com',
  12. //secure 不检查 https 的证书
  13. secure: false,
  14. }
  15. },
  16. {
  17. '/api2': {
  18. target: 'https://www.uinav2.com',
  19. secure: false,
  20. }
  21. },
  22. .
  23. .
  24. .
  25. }
  26. }
  27. })

4、修改后端接口路径

  1. const url = '/api/public/v1/home/swiperdata'

重启开发服务器,即可成功调用。

定义环境变量

让 webpack 通过读取环境变量的数据,达到在执行打包时能指定参数的功能

本章节将实现:通过环境变量的方式,指定开发服务器的端口号

核心内容:

  1. 了解 操作系统环境变量webpack 环境变量
  2. 在 webpack 中使用以上两种环境变量的方法

1、webpack 能使用的环境变量:

  • 操作系统环境变量 - 在整个操作系统中都能访问到的环境变量
  • webpack 环境变量 - 只在当前 webpack 代码中能访问到的环境变量

    步骤大纲:

  1. 改造三个 webpack 配置文件:导出函数而不是对象,并通过函数参数获取 webpack 环境变量
  2. 在快捷命令中添加 webpack 环境变量:--env port=xxxx
  3. 在开发配置文件中使用 webpack 环境变量来设置开发服务器的端口号
  4. 在开发配置文件中使用系统环境变量来设置开发服务器的端口号

具体步骤

1、将三个 webpack 的配置文件做如下改造

webpack.config.default.js

  1. module.exports = env => {
  2. return {
  3. // ...
  4. }
  5. }

webpack.config.dev.jswebpack.config.prod.js

  1. module.exports = env => {
  2. return merge(defaultConfig(env), {
  3. // ...
  4. })
  5. }

改造后即可通过 env 参数获取 webpack 环境变量。

2、在 webpack.config.dev.js 中读取 webpack 环境变量 port,设置为开发服务器的端口号
  1. // ...
  2. module.exports = env => {
  3. return merge(defaultConfig(env), {
  4. // ...
  5. devServer: {
  6. // ...
  7. port: env.port || 9000,
  8. // ...
  9. }
  10. })
  11. }

配置完毕后,就能用下列方式指定启动服务器时的端口号:

  • npm run serve 启动:使用默认端口 9000
  • npm run serve --env port=8888 启动:使用指定的端口 8888

3、更进一步:读取操作系统环境变量,设置为开发服务器端口号
// ...
module.exports = env => {
  return merge(defaultConfig(env), {
    // ...
    devServer: {
      // ...
      port: env.port || process.env.PORT || 9000,
      // ...
    }
  })
}

配置后,如以 PORT=7777 npm run serve 启动服务器,端口就会被指定为 7777

设置路径别名

为项目的 src 目录设置别名 @,让路径变得更简短易读

核心内容:

  1. 配置 webpack 路径别名
  2. 改写项目代码中的路径

步骤大纲

  1. 通过 webpack 配置项 resolve.alias 设置别名
  2. 使用别名改写代码中的相关路径

具体步骤

1、在 webpack.config.default.js 中添加别名解析配置

// ...
module.exports = env => {
  return {
    // ...
    resolve: {
      alias: {
        // 为 src 目录设置别名 @
        '@': path.resolve(__dirname, '../src')
      }
    }
  }
}

2、使用别名 @ 改写代码中的路径,比如:

import Home from '@/views/Home/index.vue'
import About from '@/views/About/index.vue'

生命周期钩子介绍

了解 webpack 生命周期的概念,以及生命周期各阶段的事件通知处理

生命周期钩子函数只能在插件plugin中使用,不能在加载器loader中使用

生命周期:Webpack 程序从开始启动到运行结束,期间经过一系列节点并触发事件所形成的一套完整事件流。

生命周期钩子:以注册监听函数的方式,来在事件发生时执行指定逻辑

Webpack 中一些较为重要的生命周期节点:

  • **before-run** - 即将开始构建
  • **run** - 已开始构建
  • **before-compile** - 即将开始编译
  • **compile** - 已开始编译
  • **make** - 正在从入口开始,分析直接和间接依赖模块,并创建出 webpack 模块对象
  • **build-module** - 正在构建 webpack 模块
  • **seal** - 封装构建结果
  • **after-compile** - 已完成构建
  • **emit** - 输出构建结果

开发自定义 Loader

自己开发 webpack loader 来解析和处理特定文件

核心内容:

  1. loader 的作用和原理
  2. 开发一个自己的 style-loader

1、loader 的作用:

webpack只认识 js 和 json 格式的代码,其他类型的文件需要借助 loader 资源加载器来转换,简单的说,loader 就是进行文件资源的转换

2、loader 的原理:

loader 就是一个函数,该函数接收一个参数,这个参数就是目标要处理的文件的源代码,例如 css-loader 里的函数参数就是css-loader 读取的css文件内容,在函数体内可以做一个逻辑操作,loader 的函数最后返回值是字符串格式的js代码

为什么需要 Loader

因为 webpack 只能直接处理 js/json 代码,其他文件都必须先转换成 js 后,才能参与打包。

而 loader 就是这样的文件转换器:它以原始文件内的数据为参数,对这些数据加工处理后,最终输出 js (及可选的 source map)

编写 loader 的语法

1、在 js 文件中通过 module.exports 导出一个函数即可:

  • 参数:要进行处理的资源内容(二进制、或字符串格式)
  • 返回值:一段代表 JS 代码的字符串
module.exports = function (source) {
  return '... 要输出的 JS 代码 ...'
}

官方指南

步骤大纲

  1. 编写一个页面,并在主入口js中引用一个自定义后缀名的文件(内容就是普通css)
  2. 配置基本的 webpack 文件
  3. 创建 loaders 目录,用于存放自定义 loader
  4. 编写自定义的 loader:my-style-loader (功能和 style-loader 一样,往页面head中插入 style 样式代码)
  5. 配置 resolveLoader.modules解析自定义 loader 的路径,并配置用 my-style-loader 处理 css 文件

具体步骤

1、创建一个新项目,并创建以下文件

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>Document</title>
  </head>
  <body>
    <div class="box"></div>
    <script src="./dist/app.bundle.js"></script>
  </body>
</html>

index.css

.box {
  width: 100px;
  height: 100px;
  border: 1px solid red;
  background-color: pink;
}

index.js

import './index.css'

2、创建自定义 Loader 代码文件: loaders/my-style-loader.js

module.exports = function (source) {
  console.log('>>>>原始文件内容>>>>', source)

  // 要输出的 JS 代码
  const newSource = `
    const style = document.createElement('style');
    style.innerHTML = \`${source}\`;
    document.head.appendChild(style);
  `

  console.log('>>>>转换后的内容>>>>', newSource)

  // 返回转换后的内容
  return newSource
}

3、创建 webpack 配置文件 webpack.config.js

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

module.exports = {
  mode: 'development',

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

  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },

  // 解析 Loader 的路径
  resolveLoader: {
    modules: [
      path.resolve(__dirname, 'loaders')
    ]
  },

  module: {
    rules: [
      // 使用 my-style-loader 处理 css 文件
      {
        test: /\.css$/i,
        use: 'my-style-loader'
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: 'index.html'
    })
  ]
}

运行 webpack build 运行index.html文件查看效果

开发自定义 Plugin

自己开发一个 webpack 插件,用它来在打包工作进行到某个环节时,执行额外的逻辑

核心内容:

  1. 插件的作用和原理
  2. 编写一个用于在打包结束后生成包含此次打包描述信息的文件

1、插件的作用

在 webpack 运行期间,会广播出许多生命周期事件,而 plugin 能监听它们并执行指定逻辑

  • 通过 webpack API 改变资源内容,达到改变最终输出内容的目的
  • 其他的任何事情,比如:将打包后的文件上传到某台服务器

编写插件的语法

插件使用 class 语法编写,它是一个带有 apply() 的类。该方法会被 webpack 编译器调用,并在整个编译周期中都有效

class MyPlugin {
  apply(compiler) {
    // 注册生命周期钩子的处理函数
    //使用 tap 给compiler.hooks.done注册函数
    compiler.hooks.done.tap('MyPlugin',(stats) => {
      console.log('Test Done!');
    });
  }
}

module.exports = MyPlugin;

tap 方法的第一个参数理论上可传任意值,但推荐传驼峰方式命名的字符串(最好就是当前类名)。

官方指南

步骤大纲

  1. 在之前项目的基础上,在 loaders 下创建插件 my-plugin
  2. 在插件中通过 stats.compilation.assets 获取打包后的资源信息
  3. 在插件中通过 compiler.options.output.path 获取打包的配置信息
  4. 对以上信息进行处理后,输出到 dist 目录中的一个文件
  5. 在 webpack 配置文件中配置 my-plugin

具体步骤

1、在之前项目的基础上,创建插件代码文件: loaders/my-plugin.js
const fs = require('fs')
const path = require('path')

class MyPlugin {

  apply(compiler) {
    // 触发打包结束事件时执行
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      // 获取生成的资源信息
      const assets = stats.compilation.assets
      const arr = Object.entries(assets).map(item => {
        // 文件名字
        const filename = item[0]
        // 文件大小
        const size = item[1].size()

        return `${filename}的文件大小是${size / 1024}KB`
      })

      // 获取打包所用时间
      const timeUsed = stats.endTime - stats.startTime

      // 生成要输入的字符串
      const res = `****** 本次构建信息 ******,
      - 构建时间:${timeUsed}毫秒,
      - 资源列表:${arr.join('\n')},
      ************************`

      // 写入文件
      const distPath = compiler.options.output.path
      fs.writeFileSync(path.join(distPath, 'report.txt'), res)
    })
  }

}

module.exports = MyPlugin

2、在 webpack 配置中启用插件

// ...
module.exports = {
    // ...
    plugins: [
        new MyPlugin()
    ]
}

打包后查看 dist/report.txt 文件:

image.png