2015 前后 打包工具时代(Webpack / Rollup / Parcel)
初步认识
问题背景(为什么要有打包工具?)
在 Grunt/Gulp 的“任务流”阶段,虽然能自动执行压缩、合并,但依然存在严重问题:
- 模块化需求爆炸
- ES6 Module(2015)被正式提出。
- Node.js 已经用 CommonJS 模块,前端也想要模块化。
- 浏览器却不支持(那时还不能直接
<script type="module">)。
👉 必须有工具来处理 依赖关系。
- 依赖复杂
- 一个文件可能
importN 个依赖,每个依赖又有更多依赖。 - 手工合并不现实,需要 自动构建依赖图(Dependency Graph)。
- 一个文件可能
- 性能问题
- 项目越来越大,加载几十上百个 JS 文件 → 网络请求太多。
- 需要合并成少量 bundle 文件,减少请求。
- 前端全面“工程化”
- Vue/React/Angular 兴起,需要单文件组件(
.vue、.jsx)。 - 需要 预处理器(Sass、TypeScript、Babel)。
- Gulp/Grunt 只是流水线,不会“理解”这些复杂结构。
- Vue/React/Angular 兴起,需要单文件组件(
👉 总结:
前端不只是要自动化,还要“打包” —— 把所有资源(JS/CSS/图片/字体)视为模块,统一管理和输出。
解决方案:打包工具的出现
1. Webpack(2014~2015 崛起)
- 核心思想:
一切皆模块(JS、CSS、图片、字体...)。
通过构建 依赖图(Dependency Graph),输出少量 bundle。 - 关键能力:
- Loader:处理各种非 JS 资源(
.css、.ts、.vue)。 - Plugin:扩展功能(压缩、优化、代码拆分)。
- HMR(热更新):只刷新改动部分。
- Code Splitting:按需加载,避免单文件过大。
- Loader:处理各种非 JS 资源(
👉 Webpack 成为前端的“工业级工厂”,几乎统治了 2015~2020 年。
2. Rollup(2015)
- 定位:专注 库/框架打包,不是应用。
- 特点:
- 天生支持 ES Module(tree-shaking 优化比 Webpack 更干净)。
- 输出格式灵活(ESM、CJS、UMD),适合发布 npm 包。
- 配置简单,但缺少 Webpack 的“全家桶”功能。
👉 Vue、React、D3.js 等库都用 Rollup 打包。
3. Parcel(2017)
- 特点:零配置上手快,自动识别依赖。
- 亮点:
- 内置 HMR、自动拆分、内置 Babel/PostCSS。
- 不需要复杂的 webpack.config.js。
- 局限:灵活度比 Webpack 低,在社区热度逐渐被 Vite 抢走。
打包工具解决了什么?
- 模块化统一
- 把 CommonJS、AMD、ESM 都能处理。
- 输出浏览器能直接运行的 bundle。
- 减少请求
- 合并 JS/CSS → 减少 HTTP 请求,提高性能。
- 工程能力提升
- TypeScript → JS
- Sass/Less → CSS
- Vue/React 单文件组件 → 普通 JS/CSS/HTML
👉 所有非原生资源都能被“编译成浏览器能跑的东西”。
- 优化生产环境
- Tree-shaking:删除无用代码。
- Code Splitting:按需加载。
- 压缩(Terser、cssnano)。
- Hash 命名:缓存优化。
阶段总结
- Grunt/Gulp(2012~2014):
自动化任务执行器(压缩、合并、刷新)。
👉 解决“体力活”,但不懂模块化。 - Webpack/Rollup/Parcel(2015~):
打包工具,构建依赖图,把一切都当模块处理。
👉 解决“模块化 + 工程化”,成为现代前端的核心。
总结:
打包工具让前端真正进入“工业化生产”时代。
它们不再只是“工人(任务流)”,而是“工厂(依赖图 + 全流程处理)”。
一切皆模块
前置思考
什么是“ 一切皆模块(JS、CSS、图片、字体...)”?模块化不是用于解决资源复用和避免全局污染的吗?这模块指的是什么?什么可以被称之为模块?怎么做到一切皆模块的?
最初的“模块”概念
最早在 JavaScript 语言层面,模块指的是 一块独立的代码,目的是解决两个问题:
- 复用:把常用逻辑抽出来,可以被不同文件引用。
- 隔离作用域:避免全局变量污染。
例子:
// math.js
export function add(a, b) { return a + b }
// main.js
import { add } from './math.js'
console.log(add(2, 3))
这里的模块就是 JavaScript 文件。
工程化后的“模块”扩展
后来,随着前端应用越来越复杂,问题出现了:
- JS 文件不仅依赖其他 JS,还依赖 CSS、图片、字体、JSON 配置 等。
- 如果每种资源都要用不同方式引入,管理混乱。
于是,Webpack、Rollup 这样的工具提出:
👉 既然模块的本质就是“一个独立、可被引用的资源单元”,那么为什么只限于 JS 呢?
于是扩展出:
- JS 模块:逻辑功能。
- CSS 模块:样式规则。
- 图片模块:图像资源(被 import 时转成 URL 或 Base64)。
- 字体模块:字形文件(同样转成 URL)。
- JSON 模块:配置数据。
- …
所以,在工程化语境下,“模块”指的是:
任何可以被依赖、被引用,并在最终构建产物里有确定表现的资源单元。
怎么做到“一切皆模块”的?
① 在构建工具出现之前
- 浏览器原生只能识别三种资源:
- HTML → 用
<link>加 CSS、用<script>加 JS。 - CSS → 用
url(...)引入图片或字体。 - JS → 只能自己写逻辑,没“模块”概念(早期只有全局变量)。
- HTML → 用
👉 在这个阶段,模块只局限于 JS 的逻辑文件,其他资源只是“外链文件”。
CSS、图片、字体,它们在浏览器眼中就是“文件”,不是“模块”。
② 构建工具带来的变化
Webpack、Rollup、Vite 出现后,它们引入了一个关键机制:
“统一依赖图(Dependency Graph)”
- 它把 所有 import/require 的东西都视为依赖节点,不管是
.js、.css、.png。 - 只要构建工具能“理解并处理”它,就能纳入依赖图。
📌 举例:
import './style.css' // 被 CSS loader 转译成 JS 注入 <style>
import logo from './logo.png' // 被 file loader 转换为 URL
import data from './data.json' // 直接变成 JS 对象
这些原本浏览器“不懂”的 import,经过 Loader/Plugin 转换 → 最终都变成 JS 能理解的内容(URL、对象、字符串…),从而能和 JS 一起纳入 同一个依赖体系。
③ 所以,“模块”定义发生了扩展
- 过去:模块 = JS 文件(语言级别限制)。
- 现在:模块 = 能被依赖、能被构建工具解析的资源单元。
换句话说:
“模块”不再是语言层面的概念,而是工程化层面的概念。
④ 为什么以前不行,现在可以?
变化点就在于:
- 浏览器本身没这个能力 → 它只认
<link>、<script>、<img>。 - 构建工具做了“预处理” → 在最终送到浏览器之前,把所有非 JS 资源转成浏览器能懂的东西。
- CSS → 被转成 JS 注入
<style>。 - 图片/字体 → 被转成 URL 或 base64,再由 JS/HTML 使用。
- JSON → 被转成 JS 对象。
- CSS → 被转成 JS 注入
因此,现在你能在代码里 import 图片,但这是因为 构建工具帮你打了“翻译”补丁。
⑤ 核心变化
变化点 = 模块的定义从“语言层面”扩展到了“构建工具层面”。
过去,浏览器只认 JS 模块(如何认识JS模块可回顾模块化的发展);现在,借助构建工具,任何文件都能被“翻译成 JS 世界能理解的依赖”,因此都能叫“模块”。
非JS类型具体如何模块化
前置思考
前面讲到了“一切皆模块”这个概念,但是在细节上依然不够清晰。JS的模块化,逻辑上比较容易理解,是按需引入JS,然后避免不同JS文件的相互干扰,但是像CSS、图片、JSON等资源,又是怎么模块化的?
① CSS 模块化
过去的问题
- CSS 是全局作用域的:
- 一个
.btn {}写在 A.css,可能影响 B 页面。 - 没有“按需加载”机制,要么写在一个大 CSS 文件,要么每个页面自己
<link>。
- 一个
模块化之后(借助构建工具 + 规范)
- 按需引入:
import './Button.css' // 只在用到 Button 组件时才会被打包
- 作用域隔离(比如 CSS Modules):
/* Button.module.css */
.btn { color: red; }
import styles from './Button.module.css'
<button className={styles.btn}>OK</button>
→ 工具会把 .btn 编译成 .btn_xxxHash,避免全局污染。
👉 总结:CSS 模块化解决了“作用域污染 + 无法按需引入”的问题。
② 图片/字体 模块化
过去的问题
- 图片、字体只能写死路径:
<img src="/static/logo.png">
- 难以做缓存、版本管理(换图片后用户浏览器可能还用旧缓存)。
模块化之后
- 可以直接在 JS 中
import:
import logo from './logo.png'
<img src={logo}>
- 构建工具会:
- 给文件加 hash:
logo.8f3ad.png(缓存更新)。 - 小图片转成 base64,避免额外请求。
- 大图片单独拎出来按需加载。
- 给文件加 hash:
👉 总结:图片/字体模块化解决了“缓存管理 + 按需使用 + 依赖统一”的问题。
③ JSON 模块化
过去的问题
- 想在前端用 JSON,要么写死在 JS 里,要么 AJAX 请求额外文件。
模块化之后
- 直接 import:
import data from './config.json'
console.log(data.apiBaseUrl)
- 构建工具会把 JSON 编译成:
export default { "apiBaseUrl": "https://xxx" }
👉 总结:JSON 模块化解决了“本地配置/数据文件能直接作为依赖使用”的问题。
统一的本质
无论是 JS、CSS、图片还是 JSON,模块化的本质都是:
- 按需引入 → 依赖图里有才打包。
- 作用域隔离 → 不影响其他模块。
- 可被构建工具管理 → 能做缓存优化、代码分割、压缩优化。
也就是说,模块化不是某种文件的天然属性,而是“被工具接管后,文件被纳入统一依赖体系”。
Node.js入门
什么是Node.js?
- Node.js 是一个独立的 JavaScript 运行环境,能独立执行 JS 代码,因为这个特点,它可以用来编写服务器后端的应用程序
- Node.js 作用除了编写后端应用程序,也可以对前端代码进行压缩,转译,整合等等,提高前端开发和运行效率
- Node.js 基于Chrome V8 引擎封装,独立执行 JS 代码,但是语法和浏览器环境的 V8 有所不同,没有 document 和 window 但是都支持 ECMAScript 标准的代码语法
- 想要得到 Node.js 需要把这个软件安装到电脑,在素材里有安装程序(window 和 mac 环境的)参考 PPT 默认下一步安装即可
Node.js 没有图形化界面,需要使用 cmd 终端命令行(利用一些命令来操控电脑执行某些程序软件)输入,node -v 检查是否安装成功
- node -v
- 需求:新建 index.js 文件,编写打印代码和 for 循环打印 3 个 6
/**
* 目标:编写 js 代码,用 node 命令执行
* 终端作用:敲击命令,调用对应程序执行
* 终端打开:目标文件->右键->在集成终端中打开
* 命令:node xxx.js (注意路径)
*/
console.log('Hello, World')
for (let i = 0; i < 3; i++) {
console.log(6)
}
- Node.js 执行目标 JS 文件,需要使用 node xxx.js 命令来执行(我们可以借助 VSCode 集成终端使用,好处:可以快速切换到目标 JS 文件所在终端目录,利用相对路径找到要执行的目标 JS 文件
fs模块-读写文件
- 模块:类似插件,封装了方法和属性供我们使用
- fs 模块:封装了与本机文件系统进行交互的,方法和属性
- fs 模块使用语法如下:
加载 fs 模块,得到 fs 对象
- const fs = require('fs')
- 写入文件内容语法:
fs.writeFile('文件路径', '写入内容', err => {
// 写入后的回调函数
})
- 读取文件内容的语法:
fs.readFile('文件路径', (err, data) => {
// 读取后的回调函数
// data 是文件内容的 Buffer 数据流
})
- 需求:向 test.txt 文件写入内容并读取打印
/**
* 目标:使用 fs 模块,读写文件内容
* 语法:
* 1. 引入 fs 模块
* 2. 调用 writeFile 写入内容
* 3. 调用 readFile 读取内容
*/
// 1. 引入 fs 模块
const fs = require('fs')
// 2. 调用 writeFile 写入内容
// 注意:建议写入字符串内容,会覆盖目标文件所有内容
fs.writeFile('./text.txt', '欢迎使用 fs 模块读写文件内容', err => {
if (err) console.log(err)
else console.log('写入成功')
})
// 3. 调用 readFile 读取内容
fs.readFile('./text.txt', (err, data) => {
if (err) console.log(err)
else console.log(data.toString()) // 把 Buffer 数据流转成字符串类型
})
path模块-路径处理
- 为何需要路径处理?
- 在 Node.js 待执行的 JS 代码中要用绝对路径,这是因为Node.js 执行 JS 代码时,代码中的路径都是以终端所在文件夹出发查找相对路径,而不是以我们认为的从代码本身出发,会遇到问题,所以在 Node.js 要执行的代码中,访问其他文件,建议使用绝对路径
const fs = require('fs')
console.log(__dirname) // D:\备课代码\2_node_3天\Node_代码\Day01_Node.js入门\代码\03
// 1. 加载 path 模块
const path = require('path')
// 2. 使用 path.join() 来拼接路径
const pathStr = path.join(__dirname, '..', 'text.txt')
console.log(pathStr)
fs.readFile(pathStr, (err, data) => {
if (err) console.log(err)
else console.log(data.toString())
})
- path.join() 方法有什么用?
- 按照所在本机系统的分隔符作为定界符来链接你传入的路径
__dirname模块内置变量的值是多少?
- 动态获取当前文件所在文件夹的绝对路径
文件压缩的原理
html文件压缩
- 前端工程化:前端代码压缩,整合,转译,测试,自动部署等等工具的集成统称,为了提高前端开发项目的效率
- 需求:把准备好的 html 文件里的回车符(\r)和换行符(\n)去掉进行压缩,写入到新 html 中
- 步骤:
- 读取源 html 文件内容
- 正则替换字符串
- 写入到新的 html 文件中,并运行查看是否能正常打开网页
- 代码如下:
/**
* 目标一:压缩 html 里代码
* 需求:把 public/index.html 里的,回车/换行符去掉,写入到 dist/index.html 中
* 1.1 读取 public/index.html 内容
* 1.2 使用正则替换内容字符串里的,回车符\r 换行符\n
* 1.3 确认后,写入到 dist/index.html 内
*/
const fs = require('fs')
const path = require('path')
// 1.1 读取 public/index.html 内容
fs.readFile(path.join(__dirname, 'public', 'index.html'), (err, data) => {
const htmlStr = data.toString()
// 1.2 使用正则替换内容字符串里的,回车符\r 换行符\n
const resultStr = htmlStr.replace(/[\r\n]/g, '')
// 1.3 确认后,写入到 dist/index.html 内
fs.writeFile(path.join(__dirname, 'dist', 'index.html'), resultStr, err => {
if (err) console.log(err)
else console.log('压缩成功')
})
})
js文件压缩
- 需求:把准备好的 JS 文件代码的回车符,换行符,打印语句去掉,并插入到之前 html 内容之后
- 步骤:
- 读取 js 文件内容
- 正则表达式替换回车符,换行符,打印语句为空字符串
- 拼接 html 代码和 js 代码,写入到新的 html 文件中
- 代码如下:
/**
* 目标二:压缩 js 里代码,并整合到 html 中一起运行
* 2.1 读取 public/index.js 内容
* 2.2 使用正则替换内容字符串里的,回车符\r 换行符\n 打印语句console.log('xxx');
* 2.3 确认后,拼接 html 内容写入到 dist/index.html 内
*/
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, 'public', 'index.html'), (err, data) => {
const htmlStr = data.toString()
const resultStr = htmlStr.replace(/[\r\n]/g, '')
// 2.1 读取 public/index.js 内容
fs.readFile(path.join(__dirname, 'public', 'index.js'), (err, data) => {
const jsStr = data.toString()
// 2.2 使用正则替换内容字符串里的,回车符\r 换行符\n 打印语句console.log('xxx');
const jsResultStr = jsStr.replace(/[\r\n]/g, '').replace(/console.log\('.+?'\);/g, '')
const result = `<script>${jsResultStr}</script>`
console.log(result)
// 2.3 确认后,拼接 html 内容写入到 dist/index.html 内
fs.writeFile(path.join(__dirname, 'dist', 'index.html'), resultStr + result, err => {
if (err) console.log(err)
else console.log('压缩成功')
})
})
})
基于http模块创建Web服务
基本实现
- 需求:引入 http 模块,使用相关语法,创建 Web 服务程序,响应返回给请求方一句提示 ‘hello,world’
- 步骤:
- 引入 http 模块,创建 Web 服务对象
- 监听 request 请求事件,对本次请求,做一些响应处理
- 启动 Web 服务监听对应端口号
- 运行本服务在终端进程中,用浏览器发起请求
- 注意:本机的域名叫做 localhost
- 代码如下:
/**
* 目标:使用 http 模块,创建 Web 服务
* Web服务:一个程序,用于提供网上信息浏览服务
* 步骤:
* 1. 引入 http 模块,创建 Web 服务对象
* 2. 监听 request 事件,对本次请求,做一些响应处理
* 3. 启动 Web 服务监听对应端口号
* 4. 运行本服务在终端,用浏览器访问 http://localhost:3000/ 发起请求(localhost 是本机域名)
* 注意:终端里启动了服务,如果想要终止按 ctrl c 停止即可
*/
// 1. 引入 http 模块,创建 Web 服务对象
const http = require('http')
const server = http.createServer()
// 2. 监听 request 事件,对本次请求,做一些响应处理
server.on('request', (req, res) => {
res.end('hello, world') // 一次请求只能对应一次响应
})
// 3. 启动 Web 服务监听对应端口号
server.listen(3000, () => {
console.log('Web 服务启动了')
})
设置支持中文字符
-
需求:让 Web 服务返回中文字符,浏览器能正确加载解析
-
步骤:给 Web 服务程序添加响应头,设置内容类型和正确的编码格式,重启 Web 服务测试访问即可
res.setHeader('Content-Type', 'text/html;charset=utf-8')
-
编码:编码是信息从一种形式或格式转换为另一种形式的过程,指的把文字在计算机里的二进制数据,用什么形式展示出来
-
utf-8编码:是一种关系映射表,也叫 utf-8 编码表,可以把中文,英文等等很多字符准确的展示出来
提供数据接口
案例1——后端代码工作过程
- 需求:基于 Web 服务,开发提供省份列表数据的接口,了解下后端的代码工作过程
- 步骤:
- 基于 http 模块,创建 Web 服务
- 使用 req.url 获取请求资源路径,并读取 province.json 理论省份数据返回给请求方
- 其他路径,暂时返回不存在的提示
- 运行 Web 服务,用浏览器发起请求测试,看是否可以获取到省份列表数据
- 代码如下:
/**
* 目标:基于 Web 服务,开发-省份列表数据接口
* 步骤:
* 1. 创建 Web 服务
* 2. 使用 req.url 获取请求的资源路径,读取 json 文件数据返回
* 3. 其他请求的路径,暂时返回不存在的提示
* 4. 运行 Web 服务,用浏览器请求地址查看效果
*/
const fs = require('fs')
const path = require('path')
// 1. 创建 Web 服务
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 2. 使用 req.url 获取请求的资源路径,读取 json 文件数据返回
if (req.url === '/api/province') {
fs.readFile(path.join(__dirname, 'data/province.json'), (err, data) => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(data.toString())
})
} else {
// 3. 其他请求的路径,暂时返回不存在的提示
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end('你要访问的资源路径不存在')
}
})
server.listen(3000, () => {
console.log('Web 服务启动了')
})
案例2——查询参数如何传递给后端
- 需求:基于刚刚的 Web 服务,开发提供城市列表数据的接口,了解下后端代码的工作过程
- 步骤:
- 判断 req.url 资源路径+查询字符串,路径前缀匹配 /api/city
- 借助 querystring 模块的方法,格式化查询字符串
- 读取 city.json 城市数据,匹配省份名字下属城市列表
- 返回城市列表,启动 Web 服务测试
- 代码如下:
/**
* 目标:基于 Web 服务,开发-城市列表数据接口
* 步骤:
* 1. 判断 req.url 资源路径+查询字符串,路径前缀匹配/api/city
* 2. 借助 querystring 模块的方法,格式化查询参数字符串
* 3. 读取 city.json 城市数据,匹配省份名字下属城市列表
* 4. 返回城市列表,启动 Web 服务测试
*/
const qs = require('querystring')
const fs = require('fs')
const path = require('path')
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 省份列表接口
if (req.url === '/api/province') {
fs.readFile(path.join(__dirname, 'data/province.json'), (err, data) => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(data.toString())
})
// 1. 判断 req.url 资源路径+查询字符串,路径前缀匹配/api/city
} else if (req.url.startsWith('/api/city')) {
// 城市列表接口
// 2. 借助 querystring 模块的方法,格式化查询参数字符串
// req.url: '/api/city?pname=辽宁省'
// 以?分隔符分割,拿到'pname=辽宁省'查询参数字符串
const str = req.url.split('?')[1]
// 把查询参数字符串 转成 JS 对象结构
const query = qs.parse(str)
// 获取前端发来的省份名字
const pname = query.pname
// 3. 读取 city.json 城市数据,匹配省份名字下属城市列表
fs.readFile(path.join(__dirname, 'data/city.json'), (err, data) => {
// 把 JSON 文件内对象格式字符串,转成对象结构
const obj = JSON.parse(data.toString())
// 省份名字作为 key,去obj对象里取到对应城市列表 value 值
const cityList = obj[pname]
// 4. 返回城市列表,启动 Web 服务测试
// 响应的是 JSON 字符串内容
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(cityList))
})
} else {
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end('你要访问的资源路径不存在')
}
})
server.listen(3000, () => {
console.log('Web 服务启动了')
})
提供网页资源
- 需求:基于 Web 服务,开发提供网页资源的功能,了解下后端的代码工作过程
- 步骤:
- 基于 http 模块,创建 Web 服务
- 使用 req.url 获取请求资源路径为 /index.html 的时候,读取 index.html 文件内容字符串返回给请求方
- 其他路径,暂时返回不存在的提示
- 运行 Web 服务,用浏览器发起请求
- 代码如下:
/**
* 目标:编写 web 服务,监听请求的是 /index.html 路径的时候,返回 dist/index.html 时钟案例页面内容
* 步骤:
* 1. 基于 http 模块,创建 Web 服务
* 2. 使用 req.url 获取请求资源路径,并读取 index.html 里字符串内容返回给请求方
* 3. 其他路径,暂时返回不存在提示
* 4. 运行 Web 服务,用浏览器发起请求
*/
const fs = require('fs')
const path = require('path')
// 1. 基于 http 模块,创建 Web 服务
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 2. 使用 req.url 获取请求资源路径,并读取 index.html 里字符串内容返回给请求方
if (req.url === '/index.html') {
fs.readFile(path.join(__dirname, 'dist/index.html'), (err, data) => {
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end(data.toString())
})
} else {
// 3. 其他路径,暂时返回不存在提示
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end('你要访问的资源路径不存在')
}
})
server.listen(8080, () => {
console.log('Web 服务启动了')
})
基于Express搭建Web服务
- Express定义链接: 基于 Node.js 平台,快速,开放,极简的 Web 开发框架
- 概念:使用 express 本地软件包,快速搭建 Web 服务(基于 http 模块)
- 功能:开发 Web 服务,提供数据接口,提供网页资源供浏览器使用
- 需求:基于 express 编写 Web 服务,对 get 请求方法和 / 路径监听,有人请求返回一段提示字符串
- 使用:
- 下载 express 本地软件包到项目中
- 导入 express 创建 Web 服务对象
- 监听请求方法和请求路径,返回一段提示字符串
- 对其他请求方法和请求路径,默认返回 404 提示
- 监听端口号,启动 Web 服务,在浏览器请求测试
- 代码如下:
/**
* 目标:基于 express 本地软件包,开发 Web 服务,返回资源给请求方
*/
// 1. 下载 express 软件包
// 2. 导入并创建 Web 服务对象
const express = require('express')
const server = express()
// 3. 监听请求的方法和请求的资源路径
server.get('/', (req, res) => {
res.send('你好,欢迎使用 Express')
})
// 4. 监听任意请求的方法和请求的资源路径
server.all('*', (req, res) => {
res.status(404)
res.send('你要访问的资源路径不存在')
})
// 5. 监听端口号,启动 Web 服务
server.listen(3000, () => {
console.log('Web 服务已启动')
})
提供数据接口
-
需求:基于 express,开发提供省份列表数据的接口
-
步骤:监听 get 请求方法的 /api/province 路径,并读取 province.json 里省份数据返回给请求方
-
核心代码:
// 监听 get 请求方法,监听资源路径 /api/province,就读取 province.json 省份数据返回
server.get('/api/province', (req, res) => {
fs.readFile(path.join(__dirname, 'data/province.json'), (err, data) => {
res.send(data.toString())
})
})
跨域问题
什么是跨域?
- 跨域:从一个源的文档/脚本,加载另一个源的资源产生了跨域
- 例如:网页文档打开时所在源和 AJAX 访问的源(协议,域名,端口)有一个不同,就发生了跨域访问
- 网页出现跨域访问时,会在浏览器控制里报错如下:
Access to XMLHttpRequest 意思为:XHR 链接出了问题,从一个源(http://localhost:5500 跨域访问 http://localhost:3000)
- 需求:在 LiveServer 的 Web 服务启动网页,用 AJAX 访问本机 Web 服务提供的省份列表接口,体验下跨域问题,index.html 代码如下:
// 目标:请求本机 Web 服务提供的省份列表数据
axios({
url: 'http://localhost:3000/api/province',
// method: 'get'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
解决方案1-CORS
-
目标:前后端分离的项目,前端和后端不在一个源,还要保证数据通信
-
解决:采用 CORS (跨域资源共享),一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),来访问加载服务器上的资源
-
思路:
- 服务器端:设置 Access-Control-Allow-Origin 响应头字段,允许除了它自己以外的源来访问自己的资源
- 前端:正常发起 AJAX 请求,无需额外操作
-
步骤:
-
下载 cors 本地软件包
-
导入 cors 函数
-
使用 server.use() 给 Web 服务添加插件功能
-
把 cors 函数调用传入给 Web 服务,启动测试
-
-
设置响应头代码
// 2. 导入 cors 函数
const cors = require('cors')
// 3. 使用 server.use() 给 Web 服务添加插件功能
server.use(cors())
解决方案2-同源访问
- 目标:开发环境用 cors,上线部署关闭 cors,并采用同源访问方式
- 做法:让后端 Web 服务即可以提供数据接口,也可以返回网页资源
- 好处:安全,后端的接口不允许非同源来访问
- 代码:express 设置暴露 public 文件夹作为静态资源目录,供浏览器直接访问,可以访问里面的 html 网页
// 暴露指定的文件夹,让前端可以直接拼接路径和资源名字来访问
server.use(express.static(path.join(__dirname, 'public')))
总结
为何要解决跨域
- 因为前后端分离的项目,不在同一个源去开发项目,需要保证数据之间通信
CORS 如何实现跨域的?只适用于什么阶段的项目?
- 让后端开启 CORS 跨域资源共享,在响应头设置 Access-Control-Allow-Origin: '*'
- CORS只适用于本地开发阶段项目
项目上线,如何解决跨域问题?
- 把前端项目和后端项目部署到同一个源下访问
Webpack时期的开发环境 vs 生产环境
为什么要区分环境?
在 Webpack(2015 前后崛起) 的语境下,前端已经进入了 模块化 + 打包工具化 的时代,项目规模越来越大,前端开发和上线部署的需求差异明显:
- 开发环境(Dev):快速迭代,调试友好。
- 生产环境(Prod):体积最小、性能最佳、加载最快、安全可靠。
👉 换句话说,同一份代码,在不同场景下需要不同的构建策略,于是 Webpack 引入了 mode 概念(development / production)。
Webpack 的两套模式
1. 开发环境(Development)
目标:提升开发体验,快速反馈。
常见特性:
- 源码调试友好:
devtool: 'cheap-module-source-map'生成 Source Map,方便定位错误到源码。 - 无压缩、无优化:不做 Uglify、Tree-shaking,保证构建速度。
- 模块热替换(HMR):只更新改动的模块,页面状态不丢失。
- DevServer 本地服务:起一个 Node.js 服务器,支持 Live Reload / API 代理。
👉 开发环境优先考虑“快”,哪怕打包结果大点、不优化都没关系。
2. 生产环境(Production)
目标:产出最优上线资源。
常见特性:
- 代码压缩:UglifyJS / Terser 压缩 JS。
- Tree-shaking:移除未使用的代码。
- 代码分割(Code Splitting):按需加载,减少首屏体积。
- 长效缓存优化:输出
bundle.[contenthash].js,结合浏览器缓存策略。 - 静态资源优化:CSS 提取(MiniCssExtractPlugin)、图片压缩。
- 环境变量替换:
process.env.NODE_ENV被替换成"production",让代码走优化逻辑。
👉 生产环境优先考虑“性能”,哪怕构建慢一些也值得。
为什么需要这种区分?
我们可以从 痛点演化 来看:
- 早期(无区分):
Grunt/Gulp 时代基本只有一份构建脚本,想要调试就注释掉压缩插件,上线再手动开启,非常麻烦。 - Webpack 设计:
官方直接定义了 mode: 'development' | 'production',帮开发者内置了两套“最佳实践”:- 开发:快 → Source Map、无压缩。
- 生产:优 → 压缩、Tree-shaking、缓存。
- 工程化思想:
区分环境意味着:同一个源码,可以按不同目标产出多份优化后的产物,适配“开发-调试”和“用户-体验”的双重需求。
总结(核心对比)
| 维度 | Development | Production |
|---|---|---|
| 构建速度 | 快,几秒钟 | 慢,可能几十秒 |
| 体积 | 大,不压缩 | 小,压缩/拆分/优化 |
| 调试体验 | 有 Source Map,错误直指源码 | Source Map 精简甚至关闭,调试不友好 |
| 热更新 | 支持 HMR | 不支持 HMR |
| 缓存 | 不关注缓存 | 使用 contenthash长效缓存 |
| 插件侧重点 | DevServer, HMR | 压缩、优化、提取 CSS |
一句话总结:
👉 开发环境是为了“开发者体验”,生产环境是为了“用户体验”。
为什么库和框架还需要打包
从代码实现的角度看
1. 源码不是最终能直接使用的形式
大多数库/框架的源码并不是「浏览器 / Node 能直接跑」的:
- 用 TypeScript 写的 → 需要转成 JS。
- 用 ES6+ 写的 → 需要转成兼容的 ES5(兼容旧环境)。
- 用 JSX/TSX 写的(React/Vue) → 需要转译成 JS 函数调用。
- 用模板语法写的(Vue SFC、Angular 模板) → 需要编译成 JS 渲染函数。
👉 也就是说,源码本身无法直接被消费,必须打包成「可执行的 JS」。
2. 模块系统不统一,需要兼容
现实情况是:不同的运行环境,支持的模块规范不一样:
- Node.js (老版本) → CommonJS (
require) - 现代浏览器 → ES Module (
import) - 老浏览器 → 不支持模块,需要 UMD/全局变量
如果库作者只写一份源码(比如 ESModule),直接发出去,很多用户的项目可能无法用。
所以库需要打包成 多份产物(CJS / ESM / UMD),确保「无论在 Node、Webpack、Vite 还是 <script> 标签里」都能用。
3. 内部依赖需要处理
库往往依赖其他第三方库:
- 有些依赖需要 内联打进产物(比如一个很小的工具函数,用户不需要单独装)。
- 有些依赖需要 标记为 external(外部依赖,比如 React,不能强行打进 Vue 产物)。
👉 这些依赖关系必须在打包时处理清楚,否则使用者会遇到「重复依赖」「版本冲突」等问题。
4. Tree Shaking 友好性
比如 lodash 源码可能写成一大坨工具函数:
// 源码(简化)
export function cloneDeep(...) { ... }
export function debounce(...) { ... }
export function throttle(...) { ... }
如果不打包处理,最终用户 import 一个函数,可能会整个文件都进 bundle,导致冗余。
库打包时(借助 Rollup 等)会把模块边界优化,使得 Tree Shaking 能生效,用户只会引入自己用到的部分。
5. 统一压缩 & 产物优化
源码里可能有:
- 注释、调试信息、测试代码
- 多余的空格、未优化的表达式
这些在「库发布」时必须清理掉,否则使用者项目里会白白变大。
所以库打包时会:
- 压缩 JS(Terser/esbuild)
- 去掉无用代码
- 产出一个「最小可用体积」的版本
从使用者的角度看
早期(jQuery、Zepto、Underscore.js 那个年代),库都是直接发布一个 .js 文件,用户 <script> 引入即可。但随着 模块化标准 的发展,情况复杂了:
- 不同的模块规范并存
- Node.js 世界:CommonJS(
require()) - 浏览器 AMD/UMD:RequireJS 等
- 现代浏览器 / 规范:ESM(
import/export)
- Node.js 世界:CommonJS(
👉 如果一个库只提供一种格式(比如只支持 ES Modules),那么:
- 旧的 Node 环境就无法直接使用。
- 打包工具(Webpack、Rollup)可能不兼容。
因此库需要通过打包,产出多种“构建产物”(CJS、ESM、UMD),以便不同环境都能使用。
实际场景
以几个实际例子说明:
- React / Vue
- 提供多种构建产物:
cjs.js、esm.js、vue.global.js,用户可以:- Node 环境直接
require('vue') - 现代打包工具
import { ref } from 'vue' <script src="vue.global.js">直接在浏览器跑
- Node 环境直接
- 提供多种构建产物:
- Lodash
- 发布了
lodash(打包好的版本) - 也有
lodash-es(纯 ES Module 版本),方便 Tree-shaking
- 发布了
- Axios
- 提供 UMD 构建 →
<script src="axios.min.js">可直接用 - 也支持 CommonJS / ESM,引入更灵活
- 提供 UMD 构建 →
👉 可以看到,打包的根本原因就是:库要适配各种运行环境,保证可用性、性能和体积控制。
总结
应用打包是为了性能优化(减少请求、缩小体积),库打包是为了分发与兼容性(多环境适配、Tree-shaking、依赖管理、减少使用者负担)。
应用 vs 库打包对比表
| 对比维度 | 应用打包(App Bundle) | 库打包(Library Bundle) |
|---|---|---|
| 主要目的 | 提升运行性能(减少请求数、缩小体积、加快加载) | 提升分发兼容性(适配不同环境、减少用户负担) |
| 目标用户 | 最终用户(直接在浏览器跑应用) | 开发者(安装库 / 框架作为依赖) |
| 入口形式 | 单一入口(index.js/ main.js/ index.html) | 多构建产物(CJS、ESM、UMD 等) |
| 对 Tree-shaking 的依赖 | 有,但更多依赖打包工具整体优化 | 极其重要,用户只想用库的一部分功能 |
| 资源整合 | 强调合并压缩:JS、CSS、图片、字体等都打到一起,减少 HTTP 请求 | 一般保持模块粒度,避免冗余,但保证能被 Tree-shaking |
| 依赖处理 | 把所有依赖都打进去(应用必须自给自足) | 常把依赖标记为 external,避免用户重复打包(如 React 不会内置 Vue) |
| 输出产物 | 通常是一个或少数几个 bundle(适配生产环境) | 多格式产物:xxx.cjs.js、 xxx.esm.js、 xxx.umd.js |
| 代码优化重点 | 压缩、拆分(code-splitting)、懒加载、缓存优化 | 保持源码清晰、模块边界明确,方便 Tree-shaking |
| 典型工具 | Webpack、Vite、Parcel | Rollup(最常见)、也可用 esbuild / tsup 等 |
| 典型案例 | 淘宝前端应用、B 站网页、后台管理系统 | Vue、React、Axios、Lodash |
总结
- 应用打包:关心最终页面能否 快、流畅。
- 库打包:关心库能否在 各种环境里顺利被使用,同时保持 轻量和可按需引入。
Webpack工具深入
什么是WebPack?
- Webpack 是一个静态模块打包工具,从入口构建依赖图,打包有关的模块,最后用于展示你的内容
- 静态模块:编写代码过程中的,html,css, js,图片等固定内容的文件
- 打包过程,注意:只有和入口有直接/间接引入关系的模块,才会被打包
- Webpack 的作用:把静态模块内容,压缩,这个和,转译等(前端工程化)
- 把 less/sass 转成 css 代码
- 把 ES6+ 降级成 ES5 等
- 支持多种模块文件类型,多种模块标准语法
- 为何不学 vite?
现在很多项目还是基于 Webpack 来进行构建的,所以还是要掌握 Webpack 的使用
- 体验 Webpack 打包 2 个 JS 文件内容
- 需求:封装 utils 包,校验用户名和密码长度,在 index.js 中使用,使用 Webpack 打包
- 步骤:
新建项目文件夹 Webpack_study,初始化包环境,得到 package.json 文件
1. <font style="color:rgb(51, 51, 51);"> npm init -y</font>
2. <font style="color:rgb(51, 51, 51);">新建 src 源代码文件夹(书写代码)包括 utils/check.js 封装用户名和密码长度函数,引入到 src/index.js 进行使用</font>
* <font style="color:rgb(51, 51, 51);">src/utils/check.js</font>
/**
* 目标:封装检验用户名和密码长度的函数
*/
export const checkUserName = uname => {
return uname.length >= 8
}
export const checkPassWord = pwd => {
return pwd.length >= 6
}
export default {
checkUserName,
checkPassWord
}
* <font style="color:rgb(51, 51, 51);">src/index.js</font>
/**
* 目标:引入工具函数使用
*/
import { checkUserName, checkPassWord } from './utils/check.js'
const unameResult = checkUserName('itheima007')
const pwdResult = checkPassWord('7654321')
console.log(unameResult, pwdResult)
下载 webpack webpack-cli 到项目(版本独立)
3. <font style="color:rgb(51, 51, 51);"> npm i webpack webpack-cli --save-dev</font>
注意:虽然 webpack 是全局软件包,封装的是命令工具,但是为了保证项目之间版本分别独立,所以这个比较特殊,下载到某个项目环境下,但是需要把 webpack 命令配置到 package.json 的 scripts 自定义命令,作为局部命令使用
项目中运行工具命令,采用自定义命令的方式(局部命令)
4. <font style="color:rgb(51, 51, 51);"> npm run build</font>
npm run 自定义命令名字
注意:实际上在终端运行的是 build 右侧的具体命名
5. <font style="color:rgb(51, 51, 51);">自动产生 dist 分发文件夹(压缩和优化后,用于最终运行的代码)</font>
Webpack 打包入口和出口
Webpack 默认入口和出口是src/index.js 和 dist/main.js,那么如何修改呢?
- Webpack 配置:影响 Webpack 打包过程
- 步骤:
- 项目根目录,新建 Webpack.config.js 配置文件
- 导出配置对象,配置入口,出口文件路径
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, 'src/main.js'), // 入口
output: { // 出口
path: path.resolve(__dirname, 'dist'),
filename: 'app.js',
clean: true // 先清空 dist,然后再输出最新内容
}
}
3. <font style="color:rgb(51, 51, 51);">重新打包观察</font>
打包案例
原始打包
- 需求:点击注册按钮,判断用户名和密码长度是否符合要求
- 步骤:
- 新建 public/index.html 准备网页模板(方便查找标签和后期自动生成 html 文件做准备)
- 核心 JS 代码写在 src/main.js 打包入口文件
/**
* 目标:点击注册,检验用户名和密码长度
*/
document.querySelector('.login-btn').addEventListener('click', () => {
const username = document.querySelector('.username').value
const password = document.querySelector('.password').value
if (!checkUserName(username)) {
alert('用户名长度要大于等于8位')
return
} else if (!checkPassWord(password)) {
alert('密码长度要求大于等于6位')
return
}
console.log('用户名和密码长度符合要求')
})
3. <font style="color:rgb(51, 51, 51);">运行自定义命令,让 Webpack 打包 JS 代码</font>
4. <font style="color:rgb(51, 51, 51);">手动复制 index.html 到 dist 下,手动引入打包后的 JS 代码文件,运行 dist/index.html 在浏览器查看效果</font>
引入插件自动生成 html 文件
- 插件 html-webpack-plugin 作用:在 Webpack 打包时生成 html 文件,并引入其他打包后的资源
- 步骤:
下载 html-webpack-plugin 本地软件包到项目中
1. <font style="color:rgb(51, 51, 51);"> npm i html-webpack-plugin --save-dev</font>
2. <font style="color:rgb(51, 51, 51);">配置 webpack.config.js 让 Webpack 拥有插件功能</font>
// ...
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [ // 插件列表
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html') // 以指定的 html 文件作为生成模板
})
]
};
3. <font style="color:rgb(51, 51, 51);">指定以 public/index.html 为模板复制到 dist/index.html,并自动引入其他打包后资源</font>
3. 运行打包命令,观察打包后 dist 文件夹下内容并运行查看效果
打包css代码
- 注意:Webpack 默认只识别 JS 和 JSON 文件内容,所以想要让 Webpack 识别更多不同内容,需要使用加载器
- 介绍需要的 2 个加载器来辅助 Webpack 才能打包 css 代码
- 加载器 css-loader:解析 css 代码
- 加载器 style-loader:把解析后的 css 代码插入到 DOM(style 标签之间)
- 步骤:
- 准备 css 文件引入到 src/mian.js 中(压缩转译处理等)
/**
* 目标:引入 css 文件内容
*/
import './css/index.css'
注意:这里只是引入代码内容让 Webpack 处理,不需定义变量接收在 JS 代码中继续使用,所以没有定义变量接收
下载 css-loader 和 style-loader 本地软件包
2. <font style="color:rgb(51, 51, 51);"> npm i css-loader style-loader --save-dev</font>
3. <font style="color:rgb(51, 51, 51);">配置 webpack.config.js 让 Webpack 拥有该加载器功能</font>
// ...
module.exports = {
// ...
module: { // 加载器
rules: [ // 规则列表
{
test: /\.css$/i, // 匹配 .css 结尾的文件
use: ['style-loader', 'css-loader'], // 使用从后到前的加载器来解析 css 代码和插入到 DOM
}
]
}
};
4. <font style="color:rgb(51, 51, 51);">打包后运行 dist/index.html 观察效果,看看准备好的样式是否作用在网页上</font>
打包less代码
- 加载器 less-loader:把 less 代码编译为 css 代码,还需要依赖 less 软件包
- 步骤:
- 准备 less 样式引入到 src/main.js 中
/**
* 目标:引入 less 文件内容
*/
import '@/less/index.less'
下载 less 和 less-loader 本地软件包
2. <font style="color:rgb(51, 51, 51);"> npm i less less-loader --save-dev</font>
3. <font style="color:rgb(51, 51, 51);">配置 webpack.config.js 让 Webpack 拥有功能</font>
// ...
module.exports = {
// ...
module: { // 加载器
rules: [ // 规则列表
// ...
{
test: /\.less$/i,
use: [
// compiles Less to CSS
'style-loader',
'css-loader',
'less-loader',
],
},
]
}
};
4. <font style="color:rgb(51, 51, 51);">打包后运行 dist/index.html 观察效果</font>
打包图片
- 资源模块:Webpack 内置了资源模块的打包,无需下载额外 loader
- 步骤:
- 准备图片素材到 src/assets 中(存放资源模块 - 图片/字体图标等)
- 在 index.less 中给 body 添加背景图
body{
background: url(../assets/background.png) no-repeat center center;
}
3. <font style="color:rgb(51, 51, 51);">在 main.js 中给 img 标签添加 logo 图片</font>
/**
* 目标:要给 img 标签设置一个 logo 图片
* 注意:再赋予给 img 的 src 属性图片的时候,需要把图片数据对象引入过来
*/
import imgObj from '@/assets/logo.png'
document.querySelector('.logo-img').src = imgObj
4. <font style="color:rgb(51, 51, 51);">配置 webpack.config.js 让 Webpack 拥有打包图片功能</font>
// ...
module.exports = {
// ...
module: { // 加载器
rules: [ // 规则列表
// ...
{ // 针对资源模块(图片,字体文件,图标文件等)处理
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset', // 根据文件大小(8KB)小于:把文件转成 base64 打包进 js 文件中(减少网络请求次数)大于:文件复制到输出的目录下
generator: { // 输出文件时,路径+名字
filename: 'assets/[hash][ext]'
}
}
]
}
};
5. <font style="color:rgb(51, 51, 51);">打包后运行 dist/index.html 观察效果</font>
3. 注意: - 小于 8KB 文件会被转成 data URI(base64 字符串)打包进 JS 文件中(好处:可以减少网络请求次数,缺点:增加 30% 体积) - 大于 8KB 文件会被复制到 dist 下,自动替换使用代码的图片名字
集成 babel 编译器
- babel 定义:是一个 JavaScript 语法编译器,将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
- babel-loader:让 Webpack 可以使用 babel 转译 JavaScript 代码
- 步骤:
- 编写一段映射数组元素,每个数值 + 1 的代码(要求用箭头函数)
/**
* 目标:让 Webpack + Babel 编译降级 JS 语法
*/
const arr = [1, 2, 3]
const result = arr.map(val => val + 1)
console.log(result)
下载 babel babel-loader core 本地软件包
2. <font style="color:rgb(51, 51, 51);"> npm i babel-loader @babel/core @babel/preset-env -D</font>
3. <font style="color:rgb(51, 51, 51);">配置 webpack.config.js 让 Webpack 拥有功能</font>
// ...
module.exports = {
// ...
module: { // 加载器
rules: [ // 规则列表
// ...
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/, // 排除指定目录里的 js (不进行编译降级)
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] // 预设规则
}
}
}
],
}
};
4. <font style="color:rgb(51, 51, 51);">打包运行 dist/index.html 观察效果</font>
4. 各个软件包的作用表格:
| 模块 | 作用 |
|---|---|
| @babel/core | Js 编译器,分析代码 |
| @babel/preset-env | babel 预设,规则 |
| babel-loader | 让 webpack 翻译 js 代码 |
Webpack 开发服务器
- 每次改动代码,都要重新打包,很麻烦,所以这里给项目集成 webpack-dev-server 开发服务器
- 作用:启动 Web 服务,打包输出源码在内存,并会自动检测代码变化热更新到网页
- 步骤;
下载 webpack-dev-server 软件包到当前项目
1. <font style="color:rgb(51, 51, 51);"> npm i webpack-dev-server --save-dev</font>
2. <font style="color:rgb(51, 51, 51);">配置自定义命令,并设置打包的模式为开发模式</font>
"scripts": {
// ...
"dev": "webpack serve --mode=development"
},
3. <font style="color:rgb(51, 51, 51);">使用 npm run dev 来启动开发服务器,访问提示的域名+端口号,在浏览器访问打包后的项目网页,修改代码后试试热更新效果</font>
在 js / css 文件中修改代码保存后,会实时反馈到浏览器
打包模式
- 打包模式:告知 Webpack 使用相应模式的内置优化
- 分类:
| 模式名称 | 模式名字 | 特点 |
|---|---|---|
| 开发模式 | development | 调试代码,实时加载,模块热替换等 |
| 生产模式 | production | 压缩代码,资源优化,更轻量等 |
- 如何设置影响 Webpack呢?
- 方式1:在 webpack.config.js 配置文件设置 mode 选项
// ...
module.exports = {
// ...
mode: 'production'
}
- <font style="color:rgb(51, 51, 51);">方式2:在 package.json 命令行设置 mode 参数</font>
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack serve --mode=development"
},
- 注意:命令行设置的优先级高于配置文件中的,推荐用命令行设置
开发环境引入source map精准定位到报错源码位置
- source map:可以准确追踪 error 和 warning 在原始代码的位置
- 问题:代码被压缩和混淆,无法正确定位源代码位置(行数和列数)
- 设置:webpack.config.js 配置 devtool 选项
// ...
module.exports = {
// ...
devtool: 'inline-source-map'
}
inline-source-map 选项:把源码的位置信息一起打包在 JS 文件内
- 注意:source map 适用于开发环境,不要在生产环境使用(防止被轻易查看源码位置)
设置解析别名路径
- 解析别名:配置模块如何解析,创建 import 或 require 的别名,来确保模块引入变得更简单
- 例如:
原来路径如下:
1. <font style="color:rgb(51, 51, 51);"> </font>`<font style="color:rgb(51, 51, 51);">import { checkUsername, checkPassword } from '../src/utils/check.js'</font>`
2. <font style="color:rgb(51, 51, 51);">配置解析别名:在 webpack.config.js 中设置</font>
// ...
module.exports = {
// ...
resolve: {
alias: {
MyUtils: path.resolve(__dirname, 'src/utils'),
'@': path.resolve(__dirname, 'src')
}
}
}
3. <font style="color:rgb(51, 51, 51);">这样我们以后,引入目标模块写的路径就更简单了</font>
import { checkUsername, checkPassword } from 'MyUtils/check.js'
import { checkUsername, checkPassword } from '@/utils/check.js'
- 修改代码的路径后,重新打包观察效果是否正常!
问题回顾
- Webpack 有什么用?
- 压缩,转译,整合,打包我们的静态模块
- Webpack 怎么用?
- 初始化环境,编写代码,安装 Webpack 软件包,配置自定义命令,打包体验查看结果
- 如何运行 package.json 里的自定义命令?
- npm run 自定义命令
- Webpack 默认入口和出口?
- src/index.js 和 dist/main.js
- 如何影响 Webpack 打包过程?
- 查文档,新建配置文件和配置属性
- Webpack 打包后的前端代码是如何运行的?
- 手动引入到 html 文件中,再交给浏览器运行
- html-webpack-plugin 插件怎么用?
- 找到插件文档,下载到项目中,配置到 Webpack 的配置文件中即可使用
- 加载器的作用是什么?
- 让 Webpack 识别更多的代码内容类型
- Webpack 支持 less 代码打包需要哪 2 个软件包?
- 需要 less less-loader 这 2 个软件包
- 资源模块指的是什么?
- 图片,字体文件等等
- babel 编译器的作用?
- 把 ECMAScript2015+ 语法向后转换,兼容低版本浏览器
- npm 下载的包如何作用在前端项目上?
- 被 Webpack 打包处理后,再引入到 html 文件中运行
- webpack-dev-server 的作用?
- 启动 Webpack 开发服务器,会启动一个 Web 服务,实时检测代码变化重新打包,并快速反应最新效果到浏览器页面上
- 开发模式和生产模式的区别?
- 开发模式注重代码热替换更快,让开发调试代码更便捷,生产模式注重项目体积更小,更轻量,适配不同的浏览器环境
- 为何打包后,在控制台无法准确定位到源码的位置信息?
- 因为 Webpack 把代码压缩和混淆了
- 路径中的 '@' 符号代表什么意思?
- 看在 webpack 配置中的别名路径是什么,就会在打包时替换成哪个路径使用