为什么要学习 Webpack
- 理解前端“工程化”概念、工具、目标
- 提高个人核心竞争力
- 成为高阶前端工程师的必经之路
什么是 Webpack
前端项目由什么构成?—— 资源
前端工程化工具出现之前靠手动管理资源文件,比如通过
<link >
标签引入样式文件,通过<script> </script>
标签引入 JS 文件等,但这种做法会出现很多问题:- 操作过程繁琐
- 存在依赖关系时要严格按照顺序书写
- 开发与生产环境一致导致难以接入 TS 或者 JS 的新特性
- 比较难接入 Less、Sass 等工具
- JS、CSS、图片资源管理模型不一致等
- 为了解决这些问题出现了许多前端工程化工具:Webpack、Vite、browserifyjs 等,某种程度上正是这些工具的出现,才有了前端工程这一概念
Webpack 本质上是一种前端资源编译、打包工具
- 多份资源文件打包成一个 Bundle
- 支持 Babel、Eslint、TS、CoffeScript、Less、Sass
- 支持模块化处理 css、图片等资源
- 支持 HMR + 开发服务器
- 支持持续监听、持续构建
- 支持代码分离
- 支持 Tree-shaking
- 支持 Sourcemap 等
Webpack 的用法
- 安装 Webpack:
npm i -D webpack webpack-cli
编辑配置文件 webpack.config.js
// webpack.config.js module.exports = { entry: './src/index.js', output: { filename: "[name].js", path: path.join(__dirname,"./dist"), }, module: { rules: [{ test: /\.less$/i, use: ['style-loader','css-loader','less-loader'] }], } }
- 执行编译命令:
npx webpack
会在根目录的 dist 文件夹下得到编译出的打包文件 main.js(默认为main.js,可以修改) - 核心流程(极度简化版):入口处理、依赖解析、资源解析、资源合并打包
Webpack 本质上完成的事情:模块化 + 一致性
- 多个文件资源合并成一个,减少 HTTP 请求数
- 支持模块化开发
- 支持 typescript、CoffeeScript 语言
- 统一图片、CSS、字体等其他资源的处理模型
使用 Webpack
关于 Webpack 的使用方法,基本都围绕“配置”展开,可划分为两大类:
- 流程类:作用于流程中的某个或若干个环节,直接影响打包效果的配置项
- 工具类:主流程之外,提供更多工程化能力的配置项
Webpack 中的属性按使用频率来看:
- entry / output
- module / plugins
- mode
- watch / devServer / devtool
- 流程类:作用于流程中的某个或若干个环节,直接影响打包效果的配置项
处理 CSS:例如在 JS 中引入 CSS 文件 import './index.css'
- 安装 Webpack:
npm i -D webpack webpack-cli
- 安装依赖:需要使用的 loader:
npm i -D style-loader css-loader
编辑配置文件 webpack.config.js 添加
module
处理 CSS 文件const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: "[name].js", path: path.join(__dirname, "./dist"), }, module: { rules: [{ // test: /\.less$/i, test: /\.css$/, use: ['style-loader', 'css-loader'] }], }, mode: 'development', }
- 终端使用
npx webpack
进行编译
使用 Webpack 处理 JavaScript:接入 Babel 将高版本 JS 代码转译成低版本 JS 代码
- 安装依赖:
npm i -D @babel/core @babel/preset-env babel-loader
编辑配置文件 webpack.config.js
const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: "[name].js", path: path.join(__dirname, "./dist"), }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.js$/, use: [{ loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ], }, }], }, ], }, mode: 'development', }
- 终端使用
npx webpack
进行编译
使用 Webpack 处理 HTML:不使用 loader 而是使用 HTML 插件
- 安装依赖
npm i -D html-webpack-plugin
编辑配置文件 webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: "[name].js", path: path.join(__dirname, "./dist"), }, module: { rules: [ { // test: /\.less$/i, test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.js$/, use: [{ loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ], }, }], }, ], }, plugins: [new HtmlWebpackPlugin], mode: 'development', }
- 终端使用
npx webpack
进行编译
使用 Webpack 进行热模块更新(HMR,Hot Module Replacement)
- 写的代码能够立刻更新到浏览器中,并且不用刷新浏览器
编辑配置文件 webpack.config.js 添加
devServer
属性module.exports = { //... devServer: { hot: true, }, }
- 启动 Webpack 使用
npx webpack serve
命令
使用 Webpack 进行 Tree-Shaking,用于删除 dead code
- dead code:代码没有被用到或者不可到达、代码的执行结果不会被用到、代码只读不写...
开启 Tree-Shaking:
mode: "production"
optimization: {usedExports: true}
module.exports = { //... mode: "production", optimization: { usedExports: true, }, }
- 终端使用
npx webpack
进行编译
Loader 组件
- Loader 的作用是进行资源内容的转化,Webpack 只认识 JS,Loader 用于处理非标准 JS 资源,翻译为标准 JS
使用 Loader(以处理 less 文件为例):
- 安装 Loader:
npm i -D css-loader style-loader less-loader
添加
module
处理 css 文件,其中 less-loader 实现了 less -> css 的转换;css-loader 将 CSS 包装成类似 module.exports = "${css}" 的内容,包装后的内容符合 JavaScript 语法;style-loader 将 CSS 模块包进 require 语句,并在运行时调用 injectStyle 等函数将内容注入到页面的 style 标签。并且这三个 loader 还是以链式调用方式加载的module.exports = { //... module: { rules: { test: /\.less$/i, use: ["css-loader","style-loader","less-loader"], }, }, }
- 安装 Loader:
Loader 的特性:
- 链式执行(前一个 loader 的输出可能是另一个 loader 的输入)
- 支持异步执行
- 分为 normal 和 pitch 两种模式
编写 Loader
一个没有任何功能的 loader:
module.exports = function(source, sourceMap?, data?){ //source 是 loader 的输入也可能是前一个 loader 的输出 return source; };
在 webpack 中调用这个 loader:
module.exports = { //... module: { rules: [ { test: /\.js$/, use: [path.join(__dirname, './loader')] }, ], }, }
- 常用 Loader
Plugin 组件
- Webpack 很多功能是靠插件实现的
- 很多知名工具像 VS Code、Webstorm 等都设计了所谓的插件架构,Webpack 本身很多功能也是基于插件实现的
不使用插件的话会有很多缺点:
- 新人需要了解整个流程细节,上手成本高功能迭代成本高,牵一发动全身
- 功能僵化,作为开源项目而言缺乏成长性
- 也就是心智成本高、可维护性低、生命力弱
- 插件架构的精髓:对扩展开放、对修改封闭
插件的使用:
- 例如
npm i -D webpack-dashboard
引入和使用插件:
// import the plugin const DashBoardPlugin = require("webpack-dashboard"); // add it to your webpack configuration plugins module.exports = { // ... plugins : [new DashBoardPlugin()], // ... };
- 例如
编写一个插件
插件围绕‘钩子’展开,钩子的作用在编译的某个环节触发钩子,某种程度上可以理解为事件
class SomePlugin { apply(compiler){ compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => {}) } }
钩子的核心信息:
- 时机:编译过程的特定节点,Webpack 会以钩子形式通知插件此刻正在发生什么事情
- 上下文:通过 tapable 提供的回调机制,以参数方式传递上下文信息
- 交互:在上下文参数对象中附带了很多存在 side effect 的交互接口,插件可以通过这些接口改变
- 时机:compier.hooks.compilation
- 参数:compilation 等
- 交互:dependencyFactories.set
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。