Vue 学习(六、扩展 - 初识 Webpack 和 Webpack 常见配置)


一、Webpack 概述


1. 前端工程化


前端工程化是一个很玄的概念,没有具体标准,其目的是为了更好的开发、维护和管理项目,一般具备如下几个特
点,就普遍被认为是前端工程化项目:

  • 模块化:以功能为维度,对 Javascript 进行划分、封装
  • 组件化:以 UI 为维度,对页面某一部分 UI 的完整实现进行提取
  • 规范化:项目结构规范,代码规范,语法规范规范等
  • 自动化:自动化打包,自动化测试,自动化部署上线等

为了实现工程化,拆分模块过多所引发的思考:

过多的模块,会导致浏览器频繁下载需要的 JS 文件

模块中误引入多余模块时,就算未使用,浏览器仍会对其进行下载

2. Webpack 静态模块打包器


为了更好的实现前端工程化,以及解决模块过多引发的问题,webpack 静态模块打包器由此诞生,其主要作用:

  • 管理:webpack 基于 node.js 环境,使用 npm 可以很方便的管理项目中的依赖
  • 打包:将项目中所有被真正使用的模块打包成一个或任意个 JS 文件,从而降低网络请求频次
  • 加载器:社区内拥有丰富的加载器,也可自定义,其可以保证源文件中的特殊语法在打包时能得到正确的加载和解析
  • 插件:webpack 生命周期中会有多次事件发生,可以利用插件来监听这些事件,更灵活的控制打包过程

webpack 官方图片,以四个核心概念(入口加载器插件出口),简单明了的展示了其最最最宏观的执行流程:
webpack官方图片
本文目标是看完可以对 Webpack 的基本使用、常见插件、加载器有个了解,如想详细学习更多的使用方式,请直接参照 Webpack 官网Webpack 中文网,如想学习其原理,请参照 Webpack 在 Github 中托管的源码

二、初次使用 Webpack


Webpack 不同版本间的使用方式可能会有差异,建议固定使用一个比较熟练的版本,或参照官网学习最新用法,本
文中使用的版本为 npm 8.1.0webpack 5.65.0webpack-cli 4.9.1


1. 初始化项目、安装 Webpack 依赖包


1) 首先必须确保拥有 node.js 环境以及 npm 包管理器,然后在项目文件夹中打开命令行,运行 npm init -y 来初始化
项目,并指定该项目由 npm 管理

初始化npm项目

2) 初始化项目成功后,命令行中继续运行 npm i webpack webpack-cli -D,来安装 webpack 相关包
安装webpack

3) webpack 相关包会被下载到同级目录的 node_modules 文件夹中,命令行中继续输入 cd node_modules/.bin
webpack -v 来验证是否安装成功

验证webpack安装
4) 项目初始化完,安装 webpack 后,项目文件夹,应该是如下结构:
初始化结构

2. 入口和出口


先理解什么是入口和出口:

入口:指定入口 JS 文件路径,让 webpack 知道需要对哪些文件进行编译、打包

出口:指定打包成功后,生成的结果文件的名字和存放位置

默认入口和出口


本文使用的 webpack 版本,其默认的入口为:项目/src/index.js,默认出口为:项目/dist/main.js

出口文件和目录是编译打包后自动生成的,所以我们只需要创建入口文件及其目录

1) 创建入口文件

新建 项目/src/index.js 入口文件,并写入如下内容:

alert('Hello Webpack!')

2) 我们还需要创建一个 html 文件,并预先引入打包后的 JS 文件,来验证是否正确打包

html 的路径无所谓,本例将其放在:项目/index.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Webpack</title>
</head>
<body>
  <!-- 预先引入打包后的出口文件 -->
  <script src="dist/main.js"></script>
</body>
</html>

3) 在 npm 配置文件中,添加自定义脚本,让其运行 webpack

修改 package.json 文件,添加自定义脚本 "build": "webpack",这样运行 npm run build 时就会执行 webpack 打包命
令 ,npm 优先使用当前目录下的 node_modules 中的 webpack 包

{
  "name": "webpack-study",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

4) 至此打包前的准备就做完了,此时项目文件夹的结构应该如下:
打包前项目结构

5) 正式使用 webpack 打包

在项目根目录,打开命令行,运行 npm run build
打包命令

6) 打包后,项目结构确认

打包后会自动生成出口的目录和文件,本例中使用的为默认出口,路径为 项目/dist/main.js,打包后结构应如下:
打包后的项目结构

7) 运行 html 文件,验证是否正确打包
验证是否正确打包

自定义入口和出口


使用 webpack 时,为了更灵活,大部分情况下,我们都是自定义入口和出口,将前例中的项目结构修改如下:
自定义入口和出口

这种结构,是不能使用默认入口和出口的,只能自定义

入口必须设置为: 项目/src/js/index.js

出口无所谓,本例暂时设置为: 项目/dist/js/main.js

1) 创建 webpack 配置文件

webpack 执行时会读取该文件,配置文件的目录和文件名可自定义,然后在使用 webpack 命令时指定配置文件的路
径就可以,此处我们先使用其默认查找的路径,创建 项目/webpack.config.js,配置文件中写入如下内容:

const path = require('path')

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

webpack 的所有配置都写在 module.exports 对象中,entry 属性用来指定入口,output 属性是一个对象类型,用来
指定出口,其 filename 属性指定出口文件名,path 属性指定出口文件路径,path 属性需要接收一个绝对路径,我们为
了防止路径写死,通过 path.resolve(__dirname, 'dist/js') 动态获取绝对路径

require 是 node.js 环境可以识别的导入模块的语法,其实现了 CommonJS 规范, require('path') 会引入 node.js
提供的内置 path 模块, __dirname 也是由 node.js 提供, 想详细了解,请查阅 node.js 相关资料


2) 修改 index.html 中的引入地址

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Webpack</title>
</head>
<body>
  <!-- 预先引入打包后的出口文件 -->
  <script src="dist/js/main.js"></script>
</body>
</html>

3) 通过 npm run build 打包,确认打包后的目录结构,最后运行 html 文件,验证是否正确打包
自定义入口和出口后打包结构

三、加载器 Loader

加载器执行时机
加载器也可以理解为预编译器,其执行时机是在 webpack 正式编译之前。因为 webpack 编译时只识别 Javascript
语法,所以我们要在正式编译之前,使用加载器,将入口文件中的所有非 Javascript 语法,转为 webpack 可以识别的语法

加载器有很多,在此简单介绍几个,加深对加载器作用的理解,更多加载器和用法请参照 Webpack 官方 LOADERS


1. 样式加载器


我们可以在 JS 模块中,使用 ES6 的模块导入语法,引入 CSS 文件,以前例为基础,做如下修改

1) 创建 CSS 文件 src/css/common.css

body {
  background-color: yellowgreen;
}
.container {
  width: 100px;
  height: 100px;
  border: 1px solid black;
  background-color: black;
}

2) 修改 index.js 文件,在文件内使用 ES6 的导入语法,将 CSS 以模块的方式进行引入

引入后以字符串的方式访问具体样式即可

import '../css/common.css'

const container = document.createElement('div')
container.className = 'container'
document.querySelector('body').append(container)

3) 安装加载器 css-loader 和 style-loader

ES6 的导入语法并不支持导入 CSS 文件,所以在 webpack 正式编译之前,我们得把导入样式的代码,转译成 ES6
识别的语法,这个时候就需要样式相关的加载器 css-loaderstyle-loader

npm i css-loader style-loader -D

css-loader 用来解析并转译入口文件中导入样式的代码,style-loader 用来将样式添加到元素

4) 修改 webpack 的配置文件 webpack.config.js

exports 对象的 module 属性,就是来配置加载器的,rule 代表拦截入口文件的规则, use 代表拦截后使用的加载器
列表,use 数组内的加载器的执行顺序是是自右向左,既先使用 css-loader 识别,再使用 style-loader 渲染

const path = require('path')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist/js')
  },
  module: {
    rules: [{
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    }]
  }
}

5) 使用 npm run build 编译,并运行验证`

编译后的目录结构如下,最后运行 index.html 确认编译结果
请添加图片描述
css-loader 这种方式预编译后,会把用到的样式,以 JS 的方式写在最终的出口 JS 文件中,如果介意最后的出口
JS 文件太大,也可以自行学习使用样式抽离加载器


2. 资源管理


不光是样式,图片等资源文件也可以使用 ES6 语法当作模块导入,以前我们处理这些资源文件,需要安装
file-loaderurl-loader 两个加载器,本来我也是想记录这两个加载器用法的,但是在写本例时,发现使用
webpack 5.65.0 中,已经默认支持资源管理,不再需要另行安装加载器了


1) 仍以前例为基础继续修改, 准备了两张图片 src/images/small.jpg (不到 10kb)src/images/big.jpg (大于 10kb)

此时的文件夹结构如下:
有图片的文件夹结构
2) 修改样式文件 - 在样式文件中使用图片

common.css 文件中,使用我们准备的图片 big.jpg

body {
  background-color: yellowgreen;
}
.container {
  width: 1200px;
  height: 500px;
  border: 1px solid black;
  background-image: url('../images/big.jpg');
}

3) 修改入口文件 - 以模块的方式导入图片后使用

index.js 文件中,先导入样式模块,让 big.jpg 与元素绑定,再将 small.jpg 图片以模块方式导入,然后使用

import '../css/common.css'
import smallImage from '../images/small.jpg'

// 容器 - container 样式中使用了图片 big.jgp
const container = document.createElement('div')
container.className = 'container'
document.querySelector('body').append(container)

// 图片 - 以模块方式导入图片,并使用
const img = document.createElement('img')
img.src = smallImage
document.querySelector('body').append(img)

4) 修改 webpack 配置文件

rules 数组中, 新增配置对象,test:资源的拦截规则,generator:文件打包后的路径,为了防止文件重名,通过
[name][hash][ext] 对图片名字进行了哈希运算,parser:当图片小于限定值时,会自动转成 base64 格式,从而减少网
络请求,此处我设置的限定值是小于 10kb 的会转成 base64 格式

具体文件修改如下:

const path = require('path')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist/js')
  },
  module: {
    rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        generator: {
          filename: '../images/[name][hash][ext]' // 以出口路径为基准
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024
          }
        }
      }
    ]
  }
}

5) npm run build 打包运行,查看结果

打包后并没有生成小图片文件,总体目录结构如下:
图片打包
运行后查看源码,小图片自动变成了 base64 格式, 大图片保持原样
图片运行效果

3. ES5 语法转换加载器


有时我们用 ES6 语法来编写的 Javascript 文件, 运行时却要兼容只支持 ES5 语法的低版本浏览器,如果手动转换
成 ES5 语法,工作量可想而知,这个时候我们就需要一个自动将 ES6 转化为 ES5 语法的加载器 Babel

继续以前面的例子为基础来做演练,在 index.js 中我们使用了 ES6 语法 const 来声明变量,我们在打包后的
main.js 中, 通过 ctrl + F 查找关键字 const,可以找到我们的对应代码

const语法
现在我们就通过安装 Babel 相关加载器,来将语法降级成 ES5

1) 使用如下命令安装 Babel 相关加载器

npm i babel-loader @babel/core @babel/preset-env -D

本例安装后的版本为 "@babel/core": "^7.16.5""@babel/preset-env": "^7.16.5""babel-loader": "^8.2.3"

2) 修改 webpack 配置文件,配置加载器

修改 webpack.config.js 文件,test:文件的拦截规则,exclude:排除对第三方包的语法降级,具体配置如下:

const path = require('path')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist/js')
  },
  module: {
    rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        generator: {
          filename: '../images/[name][hash][ext]'
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}

3) 编译后,查看生成的出口文件

npm run build 后,重新查看 main.js 文件,会发现所有 const 已经降级为 var
语法降级

四、插件


加载器的执行时机,只能在编译前,而且其主要目的,是为了对入口文件中的陌生语法进行转译,转成 webpack 识
别的 ECMAScript 语法,至于其他类别的功能,更适合插件机制,毕竟插件在 webpack 生命周期中执行时机更多


1. HTML 生成插件


细心的可以发现,在我们之前的例子中,每次打包时,并没有将我们的 index.html 打包进去,我们现在可以使用
html-webpack-plugin 来完成 HTML 打包的功能,该插件并不是将我们的 HTML 文件直接复制到打包路径,而是以
我们原 HTML 文件为模板生成一个新的 HTML 文件,并会自动在 HTML 中插入引用入口 JS 文件的语句


1) 继续以前例为基础,安装 html-webpack-plugin

npm i html-webpack-plugin -D

2) 修改 webpack 配置文件,配置插件

使用插件,需要先用 CommonJS 语法导入相应模块,一般插件名就是模块名,然后在 exports 对象内配置 plugins
数组,所有插件对象都配置在该数组内

完整配置如下:

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist/js')
  },
  module: {
    rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        generator: {
          filename: '../images/[name][hash][ext]'
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: '../index.html',
      template: 'index.html'
    })
  ]
}

new HtmlWebpackPlugin:创建插件对象,filename:生成 HTML 文件的路径和文件名,template:参照模板

3) 删除 index.html 中引入入口 JS 文件的语句

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Webpack</title>
</head>
<body>
  <!-- <script src="dist/js/main.js"></script> -->
</body>
</html>

4) npm run build 编译打包,并检查正确性

打包后的结构应如下:
打包页面
检查生成的 dist/index.html 中是否自动插入了引用入口 JS 文件的语句:

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Webpack</title>
  <script defer="defer" src="js/main.js"></script>
</head>

<body></body>

</html>

2. 小型服务器插件 - 实现热更新


我们每次对项目进行修改后,都要重新打包、重新运行 HTML 文件,这显然不符合自动化的概念,我们可以通过
使用 webpack-dev-server 插件,来解决修改后自动更新的问题

webpack-dev-server 插件,其实是一个小型服务器,我们可以将项目绑定到服务器内,然后提供 IP 和 端口以供外部
访问。它还有一个功能就是热更新,当我们对项目做修改后,会自动替换服务器内存中的信息

接着延续前例:

1) 安装 webpack-dev-server 插件

# 版本为 ^4.6.0
npm i webpack-dev-server -D

2) 配置 webpack 配置文件

想实现热更新,我们打包后的 JS 文件,必须在最终出口路径的根路径,所以我们要修改三个地方,
① 出口文件路径:path: path.resolve(__dirname, 'dist/js') 变成 path: path.resolve(__dirname, 'dist/')
② 受出口路径影响的资源路径:filename: '../images/[name][hash][ext]' 变成 filename: 'images/[name][hash][ext]'
③ 受出口路径影响的 HTML 路径:filename: '../index.html' 变成 filename: 'index.html''

最后在 exprots 对象中,添加 target 属性、mode 属性 和 devServer 对象,来配置服务器以及热更新

webpack.config.js 的完整配置如下:

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist/')
  },
  module: {
    rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        generator: {
          filename: 'images/[name][hash][ext]'
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html'
    })
  ],
  target: 'web', // Web 环境
  mode: 'development', // 开发环境
  devServer: {
    hot: true, // 开启热更新
    compress: true, // 开启 gzip 压缩
    host: '127.0.0.1', // 对外提供的 IP
    port: 8080, // 对外提供的端口
    static: {
      directory: path.join(__dirname, './dist/') // 要发布到服务器的内容
    },
    open: {
      app: {
        name: 'chrome', // 服务器启动后自动打开谷歌浏览器
        arguments: ['--incognito', '--new-window']
      }
    }
  }
}

3) 修改 NPM 管理文件

package.json 中添加新的脚本命令,此处脚本命名为 start,实际命令为 webpack serve,完整配置如下:

{
  "name": "webpack-study",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.1",
    "html-webpack-plugin": "^5.5.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.6.0"
  }
}

4) 验证能否启动服务器

先运行 npm run build 来构建出 dist 目录,构建后的目录结构如下:
构建
再运行 npm run start 命令来启动服务器,启动成功后自动打开浏览器并运行项目:
请添加图片描述

5) 验证自动更新

修改入口文件的内容,看看是否能自动更新,我是在 index.js 中加了一个 alert,然后在未打包重启的情况下,浏览
器直接自动弹出

请添加图片描述

结语:

还是那句话,本文目的是可以简单了解一下 webpack,并未深究,只要对其功能可以有一定的了解就好,让我们后
面学 Vue-CLI 可以有个基础,至于加载器和插件,官方提供的真的太多了,如果想了解更多还是要去官网研究一下。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值