前言:
当下最流行的代码构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
grunt和gulp是基于任务和流(Task、Stream)的,webpack是基于入口的。
webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。所以总的来说webpack作为目前前端开发人员必备神器,盘它!
webpack依赖Node环境,需要npm或yarn等管理工具(这两个也是目前比较主流的)
yarn下载地址: yarn.bootcss.com/docs/instal…
mac - 通过命令安装(也可还用npm)
sudo npm i -g yarn
如果上面不行, 可以试试以下方法
curl -o- -L https://yarnpkg.com/install.sh | bash
进入正题:
1.搭环境
1.1 创建一个文件夹 例:webpack-test
1.2 文件夹内初始化包环境(yarn或npm)
yarn init -y | npm init 注:-y 是按默认一键生成 package.json
1.3 安装依赖包(通过@指定版本号,按需下载)
yarn add webpack@5.31.2 webpack-cli@4.6.0 -D
1.4 在package.json中,配置scripts(自定义命令)
scripts: {
"build": "webpack"
}
2.基础使用
2.1 新建一个js文件-定义求和函数并导出 (路径:src/add/add.js)
export const addFn = (a, b) => a + b
2.2 新建src/index.js导入使用
// webpack打包的入口
import { addFn } from './add/add.js'
console.log(addFn(4, 5));
2.3 运行打包命令
yarn build
2.4生成dist文件夹
2.5 src并列处, 生成dist目录和main.js文件
2.6 查看main.js文件, 是打包压缩后的代码
(()=>{"use strict";console.log(9)})();
3.更新打包
代码变化如何打包?
3.1 新建src/tool/tool.js - 导出数组求和方法
export const getArrSum = arr => arr.reduce((sum, val) => sum += val, 0)
3.2 src/index.js - 导入使用
import { addFn } from './add/add.js'
import { getArrSum } from './tool/tool.js'
console.log(addFn(4, 5));
console.log(getArrSum([6, 7, 8, 9]));
3.3 重新打包
yarn build
3.4 自动覆盖原dist, 结果压缩极致
(()=>{"use strict";console.log(9),console.log([6,7,8,9].reduce(((o,e)=>o+e),0))})();
4. 修改默认配置
- 4.1 目标:
-
修改默认入口和出口
-
默认入口: src/index.js
-
默认出口: dist/main.js
-
- 4.2 步骤: 4.2.1 项目根目录 - 新建webpack.config.js文件 (默认配置文件名)
4.2.2 填入配置项
const path = require("path")
module.exports = {
entry: "./src/main.js", // enter: 默认入口
output: {
path: path.join(__dirname, "dist"), // 出口"文件夹"名
filename: "bundle.js" // 出口"文件"名
}
}
4.2.3 修改代码里src/index.js 为 src/main.js 4.2.4 重新打包观察输出文件名字
5.webpack-打包流程图
思考:运行yarn build发生了什么?
5.1 敲击命令, 代码执行过程
5.2 代码源文件和webpack之间关系图
源码一定要和入口产生直接/间接引入关系, 才会被一起打包
6.引入css并打包
6.1 引入jquery包
yarn add jquery
6.2 新建public/index.html - 前端首页
6.3 新建src/main.js - webpack打包入口
6.4 index.html 准备一些li
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>隔行变色</title>
</head>
<body>
<div id="app">
<!-- ul>li{我是第$个li}*10 -->
<ul>
<li>我是第1个li</li>
<li>我是第2个li</li>
<li>我是第3个li</li>
<li>我是第4个li</li>
<li>我是第5个li</li>
<li>我是第6个li</li>
<li>我是第7个li</li>
</ul>
</div>
</body>
</html>
6.5 在src/main.js引入jquery
import $ from 'jquery'
6.6 main.js中编写, 隔行变色代码
$(function() {
$('#app li:nth-child(odd)').css('color', 'red')
$('#app li:nth-child(even)').css('color', 'green')
})
6.7 执行打包命令
yarn build
6.8 把public/index.html手动复制到dist下
6.9 再手动引入打包后bundle.js
<script src="../dist/bundle.js"></script>
注意点:
-
yarn下的包, 在js文件里, 用import语法导入
浏览器不支持import语法
-
webpack翻译打包后输出到bundle.js
-
html页面引入翻译打包后的js即可正常使用
7. webpack-插件-自动生成html和css文件
思考:每次都要手动将html文件复制到,dist文件夹,很麻烦,可以配置将html也打包进dist文件夹吗? 这时就要借助插件实现。
7.1 下载插件
yarn add html-webpack-plugin@5.3.1 -D
7.2 webpack.config.js配置
// 引入自动生成 html 的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...省略其他代码
plugins: [
new HtmlWebpackPlugin({
// 以此为基准生成打包后html文件
template: './public/index.html'
})
]
}
7.3 重新打包后观察dist下
- 自动生成html文件
- 自动引入打包后js文件
7.4 思考: 在打包时, 如何把css提取成一个独立的文件?
7.5 需要依赖包 mini-css-extract-plugin
npm install mini-css-extract-plugin -D
7.6 修改配置文件webpack.config.js
// 导入webpack自动生成css的文件插件
const miniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [
new miniCssExtractPlugin({
filename: "css/[name].css" // 打包后最终生成的 dist 文件里面的 打包后的 css 文件
})
],
rules:[
{
test: /\.css$/i,
use: [
// 这个loader取代style-loader。作用:提取js中的css成单独文件
miniCssExtractPlugin.loader,
// "style-loader",
"css-loader"]
}
]
观察:打包后的dist文件夹下, 有木有多一个独立的css文件里有css代码
总结: webpack就像一个人, webpack.config.js是人物属性, 给它穿什么装备它就干什么活
8. webpack-加载器-处理css文件问题
思考:webpack能否打包css文件?
8.1 新建 - src/css/index.css
8.2 编写去除li圆点样式代码
li{
list-style: none;
}
8.3 在main.js引入index.css
一定要引入到入口才会被webpack打包
import "./css/index.css"
8.4 执行打包命令观察效果
错误解释: 你可能需要一个loader来支持这种类型文件, 解析css代码
原因: webpack默认只识别js文件
8.5 安装依赖
需要这2个模块包, 帮助webpack打包css
yarn add css-loader@5.2.1 style-loader@2.0.0 -D 注: -D 是开发依赖 一般用于开发所需
8.6 webpack.config.js 配置
module.exports = {
// ...其他代码
module: { // 如何处理项目中不同模块文件
rules: [ // 规则
{
test: /\.css$/i, // 匹配所有的css文件
// use数组里从右向左运行
// 先用 css-loader 让webpack能够识别 css 文件的内容并打包
// 再用 style-loader 将样式, 把css插入到dom中
use: [ "style-loader", "css-loader"]
}
]
}
}
8.7 执行打包命令, 观察打包后dist下
8.8 效果:
- css代码被打包进了dist/bundle.js中
- 运行时, css代码插入到html的style标签中
9. webpack-加载器-处理less文件
9.1 新建src/less/index.less - 设置li字体大小24px
@size:24px;
ul, li{
font-size: @size
}
9.2 引入到main.js中
import "./less/index.less"
9.3 下载依赖包
yarn add less@4.1.1 less-loader@8.1.0 -D
9.4 webpack.config.js 配置
module: {
rules: [
// ...省略其他
{
test: /\.less$/, // 匹配.less结尾文件
// 使用less-loader, 让webpack处理less文件, 内置还会用less模块, 翻译less代码成css代码
use: [ "style-loader", "css-loader", 'less-loader']
}
]
}
发现: 只要找到对应的loader加载器, 就能让webpack处理不同类型文件
10. webpack-加载器-处理图片文件
10.1 准备两个图片文件,放入素材文件夹
10.2 在css/less/index.less - 把小图片用做背景图
body{
background: url(../assets/logo_small.png) no-repeat center;
}
10.3 在src/main.js - 把大图插入到创建的img标签上, 添加body上显示
// 引入图片-使用
import imgUrl from './assets/1.gif'
const theImg = document.createElement("img")
theImg.src = imgUrl
document.body.appendChild(theImg)
配置: webpack5内置处理方案, 只需要填入配置即可
10.4 webpack.config.js填写
module: {
rules: [
// ...省略其他
{
test: /\.(png|jpg|gif|jpeg)$/i, // 匹配图片文件
type: 'asset' // 在导出一个 data URI 和一个单独的文件之间自动选择
}
]
}
观察: 打包后运行dist/index.html观察区别
11. webpack-加载器-图片处理区别
思考:为何图片要分2种情况处理?
- 默认8kb以下图片, 转成base64字符串打包进js中, 减少网络请求次数
- 超过8kb的图片, 直接复制到dist下, 转base64会增加30%体积
配置
11.1 asset模块区分
module: {
rules: [
// ...省略其他
{
test: /\.(png|jpg|gif|jpeg)$/i, // 匹配图片文件
type: 'asset' // 在导出一个 data URI 和一个单独的文件之间自动选择
}
]
}
12. webpack-加载器-处理字体文件
思考:webpack如何处理字体文件?
12.1 素材文件夹/字体库fonts文件夹
12.2 在main.js引入iconfont.css
// 引入字体图标文件
import './assets/fonts/iconfont.css'
12.3 在public/index.html使用字体图标样式
<i class="iconfont icon-weixin"></i>
配置
webpack5, 用asset module技术
只需简单配置webpack.config.js
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
type: 'asset/resource', // 当做静态资源直接复制文件
generator: {
filename: 'font/[name].[hash:6][ext]' // 放到dist/font文件夹, 文件名格式如左
}
}
13. webpack-加载器-处理高版本js语法
目的: 让webpack, 对高版本js语法降级
工具介绍:
babel编译器=> 用于处理高版本 js语法 的兼容性 babel官网
webpack配合babel-loader 对js语法做处理 babel-loader文档
实现:
13.1 src/main.js - 编写箭头函数
const fn = () => { // 高级语法
console.log("你好babel");
}
console.log(fn) // 一定打印函数, 才会被webpack把"函数体"打包起来
13.2 安装包
yarn add -D babel-loader@8.2.2 @babel/core@7.13.15 @babel/preset-env@7.13.15
13.3 webpack.config.js 配置规则
module: {
rules: [
{
test: /\.js$/, // 匹配js结尾文件
exclude: /(node_modules|bower_components)/, // 不转换这2个文件夹里的js
use: {
loader: 'babel-loader', // 使用加载器-处理
options: {
presets: ['@babel/preset-env'] // 预设:转码规则(用bable开发环境本来预设的)
}
}
}
]
}
观察: 打包后观察dist/的js文件, 自动变成普通函数
14. webpack-开发服务器
思考: 为何要学webpack的开发服务器?
作用: 开发时, 快速自动打包查看效果
发现问题:
-
每次修改代码, 重新打包, 才能看到最新的效果
-
实际工作中, 打包非常费时 (10-30s) 之间, 影响开发效率 分析原因:
-
构建入口和所有模块依赖关系图
-
磁盘读取对应的文件到内存, 才能加载
-
用对应的 loader 进行处理和翻译
-
将处理完的内容, 输出到磁盘指定文件内
-
以后代码变化, 从1重新开始
查看演示:
复制上一个项目
写几行代码, 想要看打包后运行效果, 打包ing....
又改了几行代码, 想看打包后运行效果, 重新打包ing...(2000 Years later..)
实现步骤:
- 构建入口和所有模块依赖关系图
- 磁盘读取对应的文件到内存, 才能加载
- 用对应的 loader 进行处理和翻译
- 将处理完的内容, 输出到内存里而非磁盘上
- 以后代码变化, 自动更新打包变化的代码, 显示到浏览器上
14.1 下载包
yarn add webpack-dev-server@3.11.2 -D
14.2 配置自定义命令serve
scripts: {
"build": "webpack",
"serve": "webpack serve"
}
14.3 运行命令-启动webpack开发服务器
yarn serve
#或者 npm run serve
14.4 启动一个web服务器和端口, 在浏览器访问查看
效果: 以后改src下的代码, 自动打包更新到浏览器上
14.5 当然webpack服务器也是可以配置的
module.exports = {
// ...其他配置
devServer: {
port: 3000, // 端口号
open: true // 启动后自动打开浏览器
}
}
观察: 重启开发服务器观察效果即可
最后把dist目录交给后台/运维, 部署给客户使用即可
常见问题1:什么是webpack?
- webpack是一个javascript的静态模块打包工具
- webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子
- 最后输出由多个模块组合成的文件,webpack专注构建模块化项目
常见问题2:webpack的优点是什么?
- 专注于处理模块化的项目,能做到开箱即用,一步到位
- 通过plugin扩展,完整好用又不失灵活
- 通过loaders扩展, 可以让webpack把所有类型的文件都解析打包
- 区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展
常见问题3:Loader和Plugin的不同?
1) 不同的作用
loader直译为"加载器"。webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
2) 不同的用法
Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。
常见问题4:有哪些常见的Loader?他们是解决什么问题的?
1、 file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
2、 url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
3、 source-map-loader:加载额外的 Source Map 文件,以方便断点调试
4、 image-loader:加载并且压缩图片文件
5、 babel-loader:把 ES6 转换成 ES5
6、 css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
7、 style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
8、 eslint-loader:通过 ESLint 检查 JavaScript 代码
常见问题5: webpack 的热更新原理
webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS(webpack-dev-server) 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
常见问题6:webpack的构建流程是什么?从读取配置到输出文件的整个过程
webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,开始执行编译
- 确定入口:根据配置中的 entry 找出所有的入口文件
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 webpack 提供的 API 改变 webpack 的运行结果