【从0开始学前端】ajax深入,Node.js,webpack
一、Ajax深入
(1)同步代码和异步代码
首先,我们要了解同步代码和异步代码分别是什么。
我们都知道在代码中,执行顺序关乎着结果,执行顺序不一样,结果就会大相径庭。所以当我们了解了同步代码和异步代码后,对我们实际的编写会有帮助。
同步代码:逐行执行,前一步的结果影响后面的结果,只有上一步进行完后才能进行下一步
异步代码:可能需要长期运行的,后面的任务不需要等待前面的任务完成,最后在完成后通过回调函数得到结果
这里举一个例子
<body>
<button class="btn">点我</button>
<script>
// 目标:阅读并回答代码执行和打印的顺序
const result = 0 + 1
console.log(result)
setTimeout(() => {
console.log(2)
}, 2000)
document.querySelector('.btn').addEventListener('click', () => {
console.log(3)
})
document.body.style.backgroundColor = 'pink'
console.log(4)
</script>
</body>
在这段代码中,const result = 0 + 1,console.log(result)优先执行,这时候输出1,之后遇到了一个函数和监听器,这两个东西优先级在console.log(4)之后,即1后输出4。注意,监听器给了btn一个按钮上,当click时候才会输出3,所以2就在3之前,之后点击按钮才会输出3。最终结果就是142,点一下输出一个3
方便记忆的一方面,赋值,打印都提前,函数,监听器都滞后
这里举几个异步代码的例子:事件,Ajax,setTimeout/setInterval
异步代码一定是通过回调函数来接收结果
(2)回调函数地狱
回调函数地狱就是在回调函数中嵌套回调函数,一直嵌套下去就成了地狱。这类代码本身可读性差,无法捕获异常,耦合性严重
举一个例子
<!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>回调地狱</title>
</head>
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:演示回调函数地狱
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
* 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
* 缺点:可读性差,异常无法获取,耦合性严重,牵一发动全身
*/
// 1. 获取默认第一个省份的名字
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
const pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 2. 获取默认第一个城市的名字
axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 3. 获取默认第一个地区的名字
axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }}).then(result => {
console.log(result)
const areaName = result.data.list[0]
document.querySelector('.area').innerHTML = areaName
})
})
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>
(3)Promise链式调用
new Promise()后的.then()方法会创建一个新的Promise对象,继续串联下一个任务,then()回调函数中的返回值会影响新生成的Promise对象最终状态和结果,可以使用链式调用解决回调函数地狱
这里举一个例子
<!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>Promise链式调用_解决回调地狱</title>
</head>
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把回调函数嵌套代码,改成Promise链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
let pname = ''
// 1. 得到-获取省份Promise对象
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 2. 得到-获取城市Promise对象
return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
}).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 3. 得到-获取地区Promise对象
return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
}).then(result => {
console.log(result)
const areaName = result.data.list[0]
document.querySelector('.area').innerHTML = areaName
})
</script>
</body>
</html>
在这段代码中,我们依次获得了省市区promise对象,要在每一次获取对象后使用.then方法,这样才是链式调用。因为是在前面的基础上再获取。
1.async函数和await关键字
async 函数是使用 async 关键字声明的函数。async 函数是AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 promise 的异步行为,而无需刻意地链式调用 promise。
在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
这里使用相同的例子
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
// 1. 定义async修饰函数
async function getData() {
// 2. await等待Promise对象成功的结果
const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
const pname = pObj.data.list[0]
const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
const cname = cObj.data.list[0]
const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
}
getData()
</script>
await是在等待前面结果后在进行本段的运行,取代了.then,可以持续的获取对象
2.async函数和await捕获错误
使用try…catch语句块
try {
//要执行的代码
} catch(error){//error接收的是,错误信息
//try里代码,如果有错误,直接进入这里执行
}
使用相同案例
<script>
/**
* 目标:async和await_错误捕获
*/
async function getData() {
// 1. try包裹可能产生错误的代码
try {
const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })
const pname = pObj.data.list[0]
const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
const cname = cObj.data.list[0]
const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
} catch (error) {
// 2. 接着调用catch块,接收错误信息
// 如果try里某行代码报错后,try中剩余的代码不会执行了
console.dir(error)
}
}
getData()
</script>
(4)事件循环
JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型
执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环
JavaScript 内代码如何执行?
➢ 执行同步代码,遇到异步代码交给宿主浏览器环境执行
➢ 异步有了结果后,把回调函数放入任务队列排队
➢ 当调用栈空闲后,反复调用任务队列里的回调函数
这里举一个例子进行联系
<script>
/**
* 目标:阅读并回答执行的顺序结果
*/
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
function myFn() {
console.log(3)
}
function ajaxFn() {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
console.log(4)
})
xhr.send()
}
for (let i = 0; i < 1; i++) {
console.log(5)
}
ajaxFn()
document.addEventListener('click', () => {
console.log(6)
})
myFn()
// 1 5 3 2 4 点击一次document就会执行一次打印6
</script>
(5)宏任务和微任务
ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务
异步任务分为:
➢宏任务:由浏览器环境执行的异步代码
➢微任务:由 JS 引擎环境执行的异步代码
在目前学习阶段,执行所在环境如下

在这里举一个例子,宏任务和微任务的嵌套
<script>
// 目标:回答代码执行顺序
console.log(1)
setTimeout(() => {
console.log(2)
const p = new Promise(resolve => resolve(3))
p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
setTimeout(() => {
console.log(4)
}, 0)
resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
// 1 7 5 6 2 3 4
</script>
(6)Promise.all静态方法
合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑
代码如下
const p=Promise.a11([Promise对象,Promise对象,...])
p.then(result =>{
//result结果:[Promise对象成功结果,Promise对象成功结果..]
}).catch(error =>{
//第一-个失败的Promise对象,抛出的异常
}
举例如下
<body>
<ul class="my-ul"></ul>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握Promise的all方法作用,和使用场景
* 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
* 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
* code:
* 北京-110100
* 上海-310100
* 广州-440100
* 深圳-440300
*/
// 1. 请求城市天气,得到Promise对象
const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })
// 2. 使用Promise.all,合并多个Promise对象
const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
p.then(result => {
// 注意:结果数组顺序和合并时顺序是一致
console.log(result)
const htmlStr = result.map(item => {
return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`//这里是映射成<li></li>的结构
}).join('')
document.querySelector('.my-ul').innerHTML = htmlStr
}).catch(error => {
console.dir(error)
})
</script>
</body>
二、Node.js和Webpack
(1)Node.js
Node.js 是一个跨平台 JavaScript 运行环境,使开发者可以搭建服务器端的 JavaScript 应用程序
作用:使用 Node.js 编写服务器端程序
✓ 编写数据接口,提供网页资源浏览功能等等
✓ 前端工程化:为后续学习 Vue 和 React 等框架做铺垫
它包含两种语法标准,CommonJS标准和ECMAScript标准
前端工程化:开发项目直到上线,过程中集成的所有工具和技术
Node.js 是前端工程化的基础(因为 Node.js 可以主动读取前端代码内容)

Node.js 为何能执行 JS?
首先:浏览器能执行 JS 代码,依靠的是内核中的 V8 引擎(C++ 程序)
其次:Node.js 是基于 Chrome V8 引擎进行封装(运行环境)
区别:都支持 ECMAScript 标准语法,Node.js 有独立的 API
注意!Node.js没有DOM和BOM

1.配置Node.js
Node.js可以让js代码独立运行,在此之前需要独立配置。
\1. 安装 Node.js
首先,确保你已经安装了 Node.js。如果没有,请从 Node.js 官网 下载并安装。
\2. 配置环境变量
如果 Node.js 已经安装,但仍然出现错误,可能是因为环境变量未正确配置。请按照以下步骤进行配置:
找到 Node.js 的安装路径。例如:D:\Node.js打开系统属性: 按 Win + R,输入 sysdm.cpl,点击确定。在系统属性窗口中,选择“高级”选项卡,然后点击“环境变量”。在系统变量中找到 Path,点击编辑。点击新建,将 Node.js 的安装路径添加进去。例如:D:\Node.js点击确定保存设置。
\3. 验证安装
配置完成后,打开新的命令提示符窗口,输入以下命令验证安装是否成功:
node -v
npm -v
如果显示 Node.js 和 npm 的版本号,则表示配置成功。
2.js文件初体验
// 目标:编写 js 代码,在 Node 环境下使用 node 命令来独立执行
console.log('Hello, Node.js')
for (let i = 0; i < 3; i++) {
console.log(6)
}
在vscode中,创建.js文件,直接输入js代码,在命令行进行运行。步骤如下:
在vscode中右击js文件,选择在集成终端中打开,此时命令行会自动切换到相应文件路径下。

在输入(node 文件名)就可以运行

3.fs模块-读写文件
模块:类似插件,封装了方法/属性
fs 模块:封装了与本机文件系统进行交互的,方法/属性
语法:
- 加载 fs 模块对象
- 写入文件内容
- 读取文件内容
结构如下
const fs = require('fs')//fs 是模块标识符:模块的名宁
fs.writeFi1e('文件路径','写入内容',err => {
//写入后的回调函数
})
fs.readFile('文件路径',(err,data)=>{
//读取后的回调函数
//data 是文件内容的 Buffer 数据流
})
体验
/**
* 目标:基于 fs 模块读写文件内容
* 1. 加载 fs 模块对象
* 2. 写入文件内容
* 3. 读取文件内容
*/
// 1. 加载 fs 模块对象
const fs = require('fs')
// 2. 写入文件内容
fs.writeFile('./test.txt', 'hello, Node.js', (err) => {
if (err) console.log(err)
else console.log('写入成功')
})
// 3. 读取文件内容
fs.readFile('./test.txt', (err, data) => {
if (err) console.log(err)
// data 是 buffer 16 进制数据流对象
// .toString() 转换成字符串
else console.log(data.toString())
})
直接读取时发现输出buffer数据流,需要使用toString输出字符串内容
4.path模块-路径处理
问题:Node.js 代码中,相对路径是根据终端所在路径来查找的,可能无法找到想要的文件
建议:在 Node.js 代码中,使用绝对路径
补充:__dirname 内置变量(获取当前模块目录-绝对路径)——拿到文件所在的文件夹
✓ windows: E:\html5\node.js和webpack\01_Node.js入门\03\index.js
注意:path.join() 会使用特定于平台的分隔符,作为定界符,将所有给定的路径片段连接在一起
语法:
- 加载 path 模块
- 使用 path.join 方法,拼接路径
path.join('03','dist/js','index.js')
//windows:'03\dist\js\index.js
//mac:'03/dist/js/index.js
const path = require('path')
path.join('路径1','路径2',...)
体验
/**
* 目标:在 Node.js 环境的代码中,应使用绝对路径
* 原因:代码的相对路径是以终端所在文件夹为起点,而不是 Vscode 资源管理器
* 容易造成目标文件找不到的错误
*/
const fs = require('fs')
// 1. 引入 path 模块对象
const path = require('path')
// 2. 调用 path.join() 配合 __dirname 组成目标文件的绝对路径
console.log(__dirname)
fs.readFile(path.join(__dirname, '../test.txt'), (err, data) => {
if (err) console.log(err)
else console.log(data.toString())
})
5.压缩案例
/**
* 目标1:压缩 html 代码
* 需求:把回车符 \r,换行符 \n 去掉,写入到新 html 文件中
* 1.1 读取源 html 文件内容
* 1.2 正则替换字符串
* 1.3 写入到新的 html 文件中
*/
// 1.1 读取源 html 文件内容
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, 'public/index.html'), (err, data) => {
if (err) console.log(err)
else {
const htmlStr = data.toString()
// 1.2 正则替换字符串
const resultStr = htmlStr.replace(/[\r\n]/g, '')//这里替换回车符 \r,换行符 \n ,g是全局替换
console.log(resultStr)
// 1.3 写入到新的 html 文件中
fs.writeFile(path.join(__dirname, 'dist/index.html'), resultStr, err => {
if (err) console.log(err)
else console.log('写入成功')
})
}
})
6.URL中的端口号
URL:统一资源定位符,简称网址,用于访问服务器里的资源
端口号:标记服务器里不同功能的服务程序
端口号范围:0-65535 之间的任意整数
例如在http://hmajax.itheima.net:80/api/province中:80就是端口号
因为http 协议,默认访问 80 端口
Web 服务程序:用于提供网上信息浏览功能
注意:0-1023 和一些特定端口号被占用,我们自己编写服务程序请避开使用
7.HTTP模块-创建Web服务
需求:创建 Web 服务并响应内容给浏览器
步骤:
- 加载 http 模块,创建 Web 服务对象
- 监听 request 请求事件,设置响应头和响应体
- 配置端口号并启动 Web 服务
- 浏览器请求 http://localhost:3000 测试
(localhost:固定代表本机的域名)
/**
* 目标:基于 http 模块创建 Web 服务程序
* 1.1 加载 http 模块,创建 Web 服务对象
* 1.2 监听 request 请求事件,设置响应头和响应体
* 1.3 配置端口号并启动 Web 服务
* 1.4 浏览器请求(http://localhost:3000)测试
*/
// 1.1 加载 http 模块,创建 Web 服务对象
const http = require('http')//请求http拿到对象
const server = http.createServer()
// 1.2 监听 request 请求事件,设置响应头和响应体——当有请求时,触发对应回调函数
server.on('request', (req, res) => {
// 设置响应头-内容类型-普通文本以及中文编码格式
res.setHeader('Content-Type', 'text/plain;charset=utf-8')
// 设置响应体内容,结束本次请求与响应
res.end('欢迎使用 Node.js 和 http 模块创建的 Web 服务')
})
// 1.3 配置端口号并启动 Web 服务
server.listen(3000, () => {
console.log('Web 服务启动成功了')
})
在成功运行代码后,服务启动,进入浏览器请求(http://localhost:3000)测试,停止服务在终端ctrl+c
(2)Node.js模块化
什么是模块化?
CommonJS 模块是为 Node.is 打包,Javascript 代码的原始方式。 Node.js 还支持浏览器和其他 JavaScript 运行时使用的 ECMAScript 模块 标准
在 Node.js 中,每个文件都被视为一个单独的模块。
概念:项目是由很多个模块文件组成的
好处:提高代码复用性,按需加载,独立作用域
使用:需要标准语法导出和导入进行使用
导出:module.exports = {}
导入:require(‘模块名或路径’)
比如,我们在上述过程中引入fs,path,http,querystring模块。
1.初体验
在这里建两个文件在同一目录下。
//第一个utils.js
/**
* 目标:基于 CommonJS 标准语法,封装属性和方法并导出
*/
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
// 导出
module.exports = {
url: baseURL,
arraySum: getArraySum
}
//第二个index.js
/**
* 目标:基于 CommonJS 标准语法,导入工具属性和方法使用
*/
// 导入
const obj = require('./utils.js')
console.log(obj)
const result = obj.arraySum([5, 1, 2, 3])
console.log(result)
在该代码中,getArraySum是求数组和,所以在调用的时候先把utils.js的方法进行了封装导出,这样就可以在别的.js文件中引用,只不过要进行导入。
2.ECMAScript 标准 - 默认导出和导入
默认标准使用:
- 导出:export default {}
- 导入:import 变量名 from ‘模块名或路径’
注意:Node.js 默认支持 CommonJS 标准语法
如需使用 ECMAScript 标准语法,在运行模块所在文件夹新建 package.json 文件,并设置 { “type” : “module” }
举一个例子。建三个文件,其中utils.js同上
//第二个package.json
{
"type": "module"
}
//第三个index.js
/**
* 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
*/
// 默认导入
import obj from './utils.js'
console.log(obj)
const result = obj.arraySum([10, 20, 30])
console.log(result)
为什么设置 { “type” : “module” }?这是因为在 package.json 中设置 { “type”: “module” } 是为了明确告诉 Node.js 将该包(项目)中的所有 .js 文件都作为 ES Modules (ESM) 来处理。
3.ECMAScript 标准 - 命名导出和导入
命名标准使用:
- 导出:export 修饰定义语句
- 导入:import { 同名变量 } from '模块名或路径‘
如何选择:
按需加载,使用命名导出和导入
全部加载,使用默认导出和导入
4.包
包:将模块,代码,其他资料聚合成一个文件夹
包分类:
✓ 项目包:主要用于编写项目和业务逻辑
✓ 软件包:封装工具和方法进行使用
要求:根目录中,必须有 package.json 文件(记录包的清单信息)
注意:导入软件包时,引入的默认是 index.js 模块文件 / main 属性指定的模块文件
//一般的 package.json
{
"name": "cz_utils",//软件包名称
"version": "1.0.0",//软件包当前版本
"description": "一个数组和字符串常用工具方法的包",//软件包描述
"main": "index.js",//软件包入口点
"author": "rain",//软件包作者
"license": "MIT"//软件包许可证
}
(3)npm
npm 是 Node.js 标准的软件包管理器
使用:
- 初始化清单文件 :npm init -y(得到 package.json 文件,有则略过此命令,先检查有无package.json文件,没有必须使用)
- 下载软件包 :npm i 软件包名称
- 使用软件包
注意,使用时内置模块和npm模块都是直接写文件名字,自己创建的需要写路径
1.安装所有依赖
在拿取别人项目后,项目不包含node_modules,不能正常运行,node_modules包含该项目中所需要的所有依赖。自己下载的速度快与拷贝的速度。
如何解决该问题?项目终端输入命令:npm i,自动会下载 package.json 中记录的所有软件包
2.全局软件包 nodemon
软件包区别:
➢ 本地软件包:当前项目内使用,封装属性和方法,存在于 node_modules
➢ 全局软件包:本机所有项目使用,封装命令和工具,存在于系统设置的位置
nodemon 作用:替代 node 命令,检测代码更改,自动重启程序
使用:
- 安装:npm i nodemon -g(-g 代表安装到全局环境中)
- 运行:nodemon 待执行的目标 js 文件
使用时nodemon 文件名,这样能够实时监测代码,输出最新结果
3.总结
/**
* 总结:
* Node.js模块:
* 概念:每个文件就是一个模块,独立作用域,按需加载,需使用特定语法导出导入
* CommonJS 标准语法:
* 导出:module.exports = {}
* 导入:require('模块名或路径')
* ECMAScript 标准语法:
* 默认导出:export default {}
* 默认导入:import 变量名 from '模块名或路径'
* 命名导出:export 修饰定义语句
* 命名导入:import { 同名变量 } from '模块名或路径'
* Node.js包:
* 概念:把模块文件,代码文件,其他资料聚合成一个文件夹
* 项目包:编写项目需求和业务逻辑的文件夹
* 软件包:封装工具/方法的文件夹(一般用 npm 管理)
* 本地软件包:封装属性/方法,在当前项目中使用,例如:dayjs,lodash
* 全局软件包:封装工具/命令,在本机中使用,例如:nodemon
* 常用命令:
* 执行 js 文件:node xxx
* 初始化 package.json: npm init -y
* 下载本地软件包:npm i 软件包名
* 下载全局软件包:npm i 软件包名 -g
* 删除软件包:npm uni 软件包名
*/
(4)Webpack
本质上,webpack 是一个用于现代 Javascript应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于展示你的内容。
静态模块:指的是编写代码过程中的,html,css,js,图片等固定内容的文件
打包:把静态模块内容,压缩,整合,转译等(前端工程化)
✓ 把 less / sass 转成 css 代码
✓ 把 ES6+ 降级成 ES5
✓ 支持多种模块标准语法
问题:为何不学 vite ?
因为:很多项目还是基于 Webpack 构建,并为 Vue
React 脚手架使用做铺垫!
所有内容都可以在webpack官网搜索
1.使用Webpack
-
新建并初始化项目,编写业务源代码
-
下载 webpack webpack-cli 到当前项目中(版本独立),并配置局部自定义命令
npm i webpack webpack-cli --save-dev进行下载
创建文件"scripts":{“build”: "webpack}这里的"build"可以更改为自定义名称
-
运行打包命令,自动产生 dist 分发文件夹(压缩和优化后,用于最终运行的代码)
npm run build运行
2.修改Webpack打包入口和出口
Webpack 配置:影响 Webpack 打包过程和结果
步骤:
- 项目根目录,新建 webpack.config.js 配置文件
- 导出配置对象,配置入口,出口文件的路径
注意:只有和入口产生直接/间接的引入关系,才会被打包
const config = {
// 打包模式(development 开发模式-使用相关内置优化)
// mode: 'development',
// 入口
// entry: path.resolve(__dirname, 'src/login/index.js'),
entry: {
'login': path.resolve(__dirname, 'src/login/index.js'),
'content': path.resolve(__dirname, 'src/content/index.js'),
'publish': path.resolve(__dirname, 'src/publish/index.js')
},
// 出口
output: {
path: path.resolve(__dirname, 'dist'),
filename: './[name]/index.js',
clean: true // 生成打包后内容之前,清空输出目录
},
3.自动创建HTML文件
插件 html-webpack-plugin: 在 Webpack 打包时生成 html 文件
步骤:
- 下载 html-webpack-plugin 本地软件包
- 配置 webpack.config.js 让 Webpack 拥有插件功能
下载时在终端输入npm i html-webpack-plugin --save-dev
配置时在plugin中
// 插件(给 Webpack 提供更多功能)
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/login.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/login/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['login'] // 引入哪些打包后的模块(和 entry 的 key 一致)
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/content.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/content/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['content']
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/publish.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/publish/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['publish']
}),
new MiniCssExtractPlugin({
filename: './[name]/index.css'
}), // 生成 css 文件
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
4.打包css代码
打包css代码需要使用加载器
加载器 css-loader:解析 css 代码
加载器 style-loader:把解析后的 css 代码插入到 DOM
步骤:
- 准备 css 文件代码引入到 src/login/index.js 中(压缩转译处理等)
- 下载 css-loader 和 style-loader 本地软件包
- 配置 webpack.config.js 让 Webpack 拥有该加载器功能
注意:Webpack 默认只识别 js 代码
下载时在终端输入npm i css-loader style-loader --save-dev
配置在module中
// 加载器(让 webpack 识别更多模块文件内容)
module: {
rules: [
// CSS 文件处理规则
{
test: /\.css$/i, // 匹配所有 .css 文件(不区分大小写)
// use: ['style-loader', "css-loader"], // 被注释的原始配置
// 根据环境选择不同的 loader
use: [
process.env.NODE_ENV === 'development'
? 'style-loader' // 开发环境:使用 style-loader 将 CSS 注入到 JS 中
: MiniCssExtractPlugin.loader, // 生产环境:提取 CSS 到独立文件
"css-loader" // 解析 CSS 文件中的 @import 和 url()
]
},
// Less 文件处理规则
{
test: /\.less$/i, // 匹配所有 .less 文件(不区分大小写)
use: [
// 将 Less 编译为 CSS
process.env.NODE_ENV === 'development'
? 'style-loader' // 开发环境:注入到 JS
: MiniCssExtractPlugin.loader, // 生产环境:提取为独立文件
'css-loader', // 解析 CSS 依赖
'less-loader', // 将 Less 编译为 CSS
],
},
// 图片文件处理规则
{
test: /\.(png|jpg|jpeg|gif)$/i, // 匹配常见图片格式
type: 'asset', // Webpack 5 的资源模块类型
generator: {
filename: 'assets/[hash][ext][query]'
// 输出路径和命名规则:
// assets/ 目录下
// [hash] 文件内容哈希值
// [ext] 原文件扩展名
// [query] 可能的查询参数
}
}
],
},
5.提取css代码
插件 mini-css-extract-plugin:提取 css 代码
步骤:
- 下载 mini-css-extract-plugin 本地软件包
- 配置 webpack.config.js 让 Webpack 拥有该插件功能
下载时在终端输入npm install --save-dev mini-css-extract-plugin
格式如下
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
// ...
module: {
rules: [
{
test: /\.css/i,
// use: ['style-loader', 'css-loader']
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
// ...
new MiniCssExtractPlugin()
]
};
6.压缩css代码
使用 css-minimizer-webpack-plugin 插件
步骤:
- 下载 css-minimizer-webpack-plugin 本地软件包
- 配置 webpack.config.js 让 webpack 拥有该功能
下载时在终端输入npm install css-minimizer-webpack-plugin --save-dev
格式如下
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
// ...
// 优化
optimization: {
// 最小化
minimizer: [
// 在 webpack5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 terser-webpack-plugin),将下一行取消注释(保证 JS 代码还能被压缩处理)
// ...,
new CssMinimizerPlugin(),
],
}
};
7.打包less代码
加载器 less-loader:把 less 代码编译为 css 代码
步骤:
- 新建 less 代码(设置背景图)并引入到 src/login/index.js 中
- 下载 less 和 less-loader 本地软件包
- 配置 webpack.config.js 让 Webpack 拥有功能
注意:less-loader 需要配合 less 软件包使用
下载时在终端输入npm i less less-loader --save-dev
格式如下
// ...
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.less$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
}
]
}
}
8.打包图片
资源模块:Webpack5 内置资源模块(字体,图片等)打包,无需额外 loader
步骤:
配置 webpack.config.js 让 Webpack 拥有打包图片功能
✓ 占位符 【hash】对模块内容做算法计算,得到映射的数字字母组合的字符串
✓ 占位符 【ext】使用当前模块原本的占位符,例如:.png / .jpg 等字符串
✓ 占位符 【query】保留引入文件时代码中查询参数(只有 URL 下生效)
注意:判断临界值默认为 8KB
✓ 大于 8KB 文件:发送一个单独的文件并导出 URL 地址
✓ 小于 8KB 文件:导出一个 data URI(base64字符串
格式如下
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset',
generator: {
filename: 'assets/[hash][ext][query]'
}
}
]
}
}
9.搭建开发环境
开发环境:配置 webpack-dev-server 快速开发应用程序
作用:启动 Web 服务,自动检测代码变化,热更新到网页
注意:dist 目录和打包内容是在内存里(更新快)
步骤:
- 下载 webpack-dev-server 软件包到当前项目
- 设置模式为开发模式,并配置自定义命令
- 使用 npm run dev 来启动开发服务器,试试热更新效果/实时更新
下载时在终端输入npm i webpack-dev-server --save-dev
// ...设置开发模式并配置自定义命令
module.exports = {
// ...
mode: 'development'
}
"scripts": {
// ...
"dev": "webpack serve --mode=development"
},
10打包模式的应用
方案1:webpack.config.js 配置导出函数,但是局限性大(只接受 2 种模式)
方案2:借助 cross-env (跨平台通用)包命令,设置参数区分环境
步骤:
方案3:配置不同的 webpack.config.js (适用多种模式差异性较大情况)
- 下载 cross-env 软件包到当前项目
- 配置自定义命令,传入参数名和值(会绑定到 process.env 对象下)
- 在 webpack.config.js 区分不同环境使用不同配置
下载时在终端输入npm i cross-env --save-dev
module: {
rules: [
{
test: /\.css$/i,
// use: ['style-loader', "css-loader"],
use: [process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.less$/i,
use: [
// compiles Less to CSS
process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
}
],
},
11.Webpack 开发环境调错 source map
- source map:可以准确追踪 error 和 warning 在原始代码的位置
- 问题:代码被压缩和混淆,无法正确定位源代码位置(行数和列数)
- 设置:webpack.config.js 配置 devtool 选项
- 注意:source map 适用于开发环境,不要在生产环境使用(防止被轻易查看源码位置)
// ...
module.exports = {
// ...
devtool: 'inline-source-map'
}
12.设置解析别名路径
原来路径如下:
import { checkPhone, checkCode } from '../src/utils/check.js'
配置解析别名:在 webpack.config.js 中设置
// ...
const config = {
// ...
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
这样以后引入目标模块写的路径就更简单了
import { checkPhone, checkCode } from '@/utils/check.js'
13.多页面打包
单页面:单个 html 文件,切换 DOM 的方式实现不同业务逻辑展示,后续 Vue/React 会学到
多页面:多个 html 文件,切换页面实现不同业务逻辑展示
需求:把黑马头条-数据管理平台-内容页面一起引入打包使用
步骤:
- 准备源码(html,css,js)放入相应位置,并改用模块化语法导出
- 下载 form-serialize 包并导入到核心代码中使用
- 配置 webpack.config.js 多入口和多页面的设置
// ...
const config = {
entry: {
'模块名1': path.resolve(__dirname, 'src/入口1.js'),
'模块名2': path.resolve(__dirname, 'src/入口2.js'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: './[name]/index.js'
}
plugins: [
new HtmlWebpackPlugin({
template: './public/页面2.html', // 模板文件
filename: './路径/index.html', // 输出文件
chunks: ['模块名2']
})
new HtmlWebpackPlugin({
template: './public/页面2.html', // 模板文件
filename: './路径/index.html', // 输出文件
chunks: ['模块名2']
})
]
}
14.分割公共代码
需求:把 2 个以上页面引用的公共代码提取
配置 webpack.config.js 的 splitChunks 分割功能
// ...
const config = {
// ...
optimization: {
// ...
splitChunks: {
chunks: 'all', // 所有模块动态非动态移入的都分割分析
cacheGroups: { // 分隔组
commons: { // 抽取公共模块
minSize: 0, // 抽取的chunk最小大小字节
minChunks: 2, // 最小引用数
reuseExistingChunk: true, // 当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用
name(module, chunks, cacheGroupKey) { // 分离出模块文件名
const allChunksNames = chunks.map((item) => item.name).join('~') // 模块名1~模块名2
return `./js/${allChunksNames}` // 输出到 dist 目录下位置
}
}
}
}
15.代码总结演示
仅包含webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const webpack = require('webpack')
const config = {
// 打包模式(development 开发模式-使用相关内置优化)
// mode: 'development',
// 入口
// entry: path.resolve(__dirname, 'src/login/index.js'),
entry: {
'login': path.resolve(__dirname, 'src/login/index.js'),
'content': path.resolve(__dirname, 'src/content/index.js'),
'publish': path.resolve(__dirname, 'src/publish/index.js')
},
// 出口
output: {
path: path.resolve(__dirname, 'dist'),
filename: './[name]/index.js',
clean: true // 生成打包后内容之前,清空输出目录
},
// 插件(给 Webpack 提供更多功能)
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/login.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/login/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['login'] // 引入哪些打包后的模块(和 entry 的 key 一致)
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/content.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/content/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['content']
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/publish.html'), // 模板文件
filename: path.resolve(__dirname, 'dist/publish/index.html'), // 输出文件
useCdn: process.env.NODE_ENV === 'production', // 生产模式下使用 cdn 引入的地址
chunks: ['publish']
}),
new MiniCssExtractPlugin({
filename: './[name]/index.css'
}), // 生成 css 文件
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
// 加载器(让 webpack 识别更多模块文件内容)
module: {
rules: [
{
test: /\.css$/i,
// use: ['style-loader', "css-loader"],
use: [process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.less$/i,
use: [
// compiles Less to CSS
process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset',
generator: {
filename: 'assets/[hash][ext][query]'
}
}
],
},
// 优化
optimization: {
// 最小化
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释(保证 js 代码还能压缩)
`...`,
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: 'all', // 所有模块动态非动态移入的都分割分析
cacheGroups: { // 分隔组
commons: { // 抽取公共模块
minSize: 0, // 抽取的chunk最小大小字节
minChunks: 2, // 最小引用数
reuseExistingChunk: true, // 当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用
name(module, chunks, cacheGroupKey) { // 分离出模块文件名
const allChunksNames = chunks.map((item) => item.name).join('~') // 模块名1~模块名2
return `./js/${allChunksNames}` // 输出到 dist 目录下位置
}
}
}
}
},
// 解析
resolve: {
// 别名
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
// 开发环境下使用 sourcemap 选项
if (process.env.NODE_ENV === 'development') {
config.devtool = 'inline-source-map'
}
// 生产环境下使用相关配置
if (process.env.NODE_ENV === 'production') {
// 外部扩展(让 webpack 防止 import 的包被打包进来)
config.externals = {
// key:import from 语句后面的字符串
// value:留在原地的全局变量(最好和 cdn 在全局暴露的变量一致)
'bootstrap/dist/css/bootstrap.min.css': 'bootstrap',
'axios': 'axios',
'form-serialize': 'serialize',
'@wangeditor/editor': 'wangEditor'
}
}
module.exports = config
更多推荐

所有评论(0)