一、工程化
大约 23 分钟约 6964 字
(一)模块化
1.为什么需要模块化
- 当前端工程到达一定规模后,就会出现以下问题
- 全局变量污染
- 依赖混乱
- 共同导致了 代码文件难以细分
- 模块化就是为了解决上面两个问题出现的
- 可以把臃肿的代码细分到各个小文件中
- 便于后期维护管理
2.前端模块化标准
1)CommonJS
- 简称 CMJ
- 是一个 社区 规范
- 出现时间较早,目前仅 node 环境支持
2)ES Module
- 简称 ESM
- 随着 ES6 发布的 官方 模块化标准
- 目前浏览器和新版本 node 环境(>12.0)均支持
3)node 环境
(二)CommonJS
标准类型:社区规范
支持环境:node
依赖类型:动态依赖
1.CommonJS 如何实现模块化
- node 天生支持 CommonJS 模块化标准
const xxx = require('xxxx');
1)CMJ 规范(Node 规定)
- node 中的每个 js 文件都是一个 CMJ 模块
- 通过 node 命令运行的模块,叫做入口模块
- 模块中的所有全局定义的变量、函数,都不会污染到其他模块
- 模块可以暴露(导出)一些内容给其他模块使用
- 需要暴露什么内容,就在模块中给
module.exports
赋值
- 需要暴露什么内容,就在模块中给
- 一个模块可以导入其他模块
- 使用函数
require("要导入的模块路径")
即可完成- 该函数返回目标模块的导出结果
- 导入模块时,可以省略
.js
- 导入模块时,必须以
./
或../
开头
- 使用函数
- 一个模块在被导入时会运行一次,然后它的导出结果会被 node 缓存起来
- 后续对该模块导入时,不会重新运行,直接使用缓存结果
2.导出
module.exports = 导出的值;
3.导入
require("模块路径"); // 函数返回模块导出的值
(三)ES Module
标准类型:官方标准
支持环境:node,浏览器
依赖类型:静态依赖,动态依赖
- 静态依赖:代码执行前就先分析模块间的依赖关系
- 动态依赖:代码执行时遇到 import 再去分析模块间的依赖关系
1.导出
- ES Module 分为两种导出方式:
- 具名导出(普通导出),可以导出多个
- 默认导出,只能导出一个
- 一个模块可以同时存在两种导出方式
- 最终会合并为一个 对象 导出
- 具名导出的变量作为该对象的属性
- 默认导出的值只能赋给
default
这个属性
{
default: xxx; // 默认导出
a: 1, // 具名导出
b: '1', // 具名导出
}
1)具名导出
- export 后要跟定义语句
- 不能是表达式,如:
export a
export const a = 1;
export function b() {}
export const c = () => {};
const d = 2;
export { d };
const k = 10;
export { k as temp };
const f = 4,
g = 5,
h = 6;
export { f, g, h as default }; // 基本 + 默认
// 以上代码将导出下面的对象
/*
{
a: 1,
b: fn,
c: fn,
d: 2,
temp: 10,
f: 4,
g: 5,
default: 6
}
*/
2)默认导出
export default 3
export default function() {}
const e = 4;
export { e as default }
const f = 4, g = 5, h = 6
export { f, g, h as default} // 基本 + 默认
// 以上代码将导出下面的对象
/*
{
a: 1,
b: fn,
c: fn,
d: 2,
temp: 10,
f: 4,
g: 5,
default: 6
}
*/
注意
- 导出代码必须为顶级代码,即不可放到代码块中
- 尽量不要直接导出变量
export let str = 'js';
不推荐
- 应该导出常量,或者导出的变量放在函数中
export const str = 'js';
推荐export const getStr = () => str;
推荐
2.导入
- 针对具名导出和默认导出,有不同的导入语法
- 导入模块时, 不可以 省略
.js
1)静态导入
/**
* 常用
*/
// 仅运行一次该模块,不导入任何内容
import "模块路径";
// 导入属性 a、b,放到变量a、b中。a->a, b->b
import { a, b } from "模块路径";
// 导入属性 default,放入变量c中。default->c
import c from "模块路径";
// default->c,a->a, b->b
import c, { a, b } from "模块路径";
// 将模块对象放入到变量obj中
import * as obj from "模块路径";
/**
* 不常用
*/
// 导入属性a、b,放到变量temp1、temp2 中
import { a as temp1, b as temp2 } from "模块路径";
// 导入属性default,放入变量a中,default是关键字,不能作为变量名,必须定义别名
import { default as a } from "模块路径";
//导入属性default、b,放入变量a、b中
import { default as a, b } from "模块路径";
注意
- 静态导入的代码必须为在代码顶端,不可放入代码块中
- 静态导入的代码绑定的符号是常量,不可更改
2)动态导入
- 返回一个 Promise,完成时的数据为模块对象
import("模块路径");
setTimeout(() => {
import("./math.js").then((m) => {
const math = m.default;
console.log(math.sum(1, 2), math.isOdd(5));
});
}, 1000);
提示
- Webpack 在 config.js 中使用 ESM 的 import 语法报错时
- 可以将文件名修改为
config.mjs
会识别 ESM 语法 - 也可以在 package.json 文件中配置
type: "module"
- 可以将文件名修改为
- 如果配置了
type: "module"
又使用了require
等 commonjs 的语法,文件名需使用.cjs
- 某些使用 CommonJS 语法导出的第三方库,没有 default 默认导出
import react from 'react'
这种 bare import 语法会报错- 不报错是因为脚手架处理了
- 如果没有使用脚手架,又需要将 CommonJS 写的库默认导出
- 需要使用
import * as react from 'react'
- 需要使用
from 'react'
的写法本身在 esm 中也是不支持的,必须写完整路径- 不报错是因为脚手架处理了路径
(四)包管理器
npm 官网:https://www.npmjs.com/
1.概念
1)包
- 包(package)是一个或多个 js 模块的集合
- 共同完成某一类功能
- 可以简单的认为每一个工程就是一个包
- 有些包是为了给别人用的
- 也叫第三方库
2)包管理器
- 包管理器是一个管理包的工具
- 前端常见的包管理器有 npm、yarn、cnpm、 pnpm 等
- 包管理器具备以下能力:
- 让开发者可以轻松的下载包
- 让开发者可以轻松的升级和卸载包
- 能够自动管理包的依赖
peerdependency 对等依赖
- npm 安装某些第三方库时会报错,终止安装
- 原因:第三方库将某些库的版本锁死,本地库的版本不兼容
- 推荐直接忽略报错,改成 pnpm 安装,只会警告不会报错
- 或者使用
npm i --force--legacy-dep
强制安装
3)CLI
- CLI 是一个命令行工具
- 提供一个终端命令,通过该命令可以完成一些功能
4)构建工具
- Webpack
- Vite
- ESBuild
- Rollup
- SnowPack
- Parcel
- TSup
2.node 查找包的顺序
require("a");
- 查找是否有内置模块 a
- 查找当前目录的 node_modules 中是否有 a
- 依次查找上级目录的 node_modules 中是否有 a,直到根目录
3.配置源
1)查看源
npm config get registry
2)配置淘宝镜像源
npm config set registry https://registry.npm.taobao.org
3)配置官方源
npm config set registry https://registry.npmjs.org/
4.初始化
# 初始化工程,帮助生成 package.json 文件
npm init
# 初始化工程,全部使用默认配置生成 package.json 文件
npm init -y
5.package.json
{
"dependencies": {
// 本地普通依赖
"qrcode": "^1.4.4" // 依赖包qrcode,版本1.4.4,主版本号不变,此版本号和补丁版本可增
},
"devDenpendencies": {
// 开发依赖
"webpack": "^5.0.0"
}
}
6.安装
1)本地安装
- 会将包下载到当前命令行所在目录的 node_modules 中
- 绝大部分安装都使用本地安装
- install 可以替换为 i
npm install 包名
npm install --save 包名
npm install 包名@版本号
- 若仅作为开发依赖,则添加参数
-D
npm i -D @types/node
node 语法提示插件
npm install -D 包名
npm install -D 包名@版本号
- 若要还原安装
npm install
# 仅还原dependencies中的依赖
npm install --production
2)全局安装
- 会将包下载到一个全局的位置
- 只有需要使用某个全局命令时,才需要进行全局安装
- install 可以替换为 i
npm install -g 包名
npm install -g 包名@版本号
7.卸载
1)本地卸载
- 卸载本地的安装包
- uninstall 均可替换为 un
npm uninstall 包名
2)全局卸载
- 卸载全局的安装包
- uninstall 均可替换为 un
npm uninstall -g 包名
8.查看包信息
1)查看包所有的版本
- view 可以替换为 v
npm view 包名 versions
(五)Less
1.概念
- Less 是一种更加简洁的样式代码
- 非常像 CSS,但又不太一样,让编写样式变得更容易
- 下面是 css 代码和 Less 代码的对比,都表达了一样的含义
- Less 代码虽好,但无法被浏览器识别
- 因此需要一个工具将其转换为血统纯正的 css 代码
- node 环境具有读写文件的能力
- 所以在 node 环境中可以轻松的完成文件的转换
npm
上有一个包叫做less
- 运行在 node 环境中
- 可以完成对 Less 代码的转换
- node 环境在前端工程化中,充当了一个辅助的角色
- 并不直接运行前端代码,而是让编写的前端代码更加舒适便利
相关信息
- 转换代码,称之为编译(compile)
- 转换代码的工具,称之为编译器(compiler)
2.体验 Less
index.less
文件
1)新建 @green: #008c8c;
.list {
display: flex;
flex-wrap: wrap;
color: @green;
li {
margin: 1em;
&:hover {
background: @green;
color: #fff;
}
}
}
npm
下载 less
2)使用 less
包提供了一个 CLI 工具lessc
方案一:全局安装 less
- 可以在任何终端目录使用
lessc
命令 - 不利于版本控制
- 可以在任何终端目录使用
方案二:本地安装 less
- 会把
less
安装到工程目录的node_modules
中 - 无法全局使用
lessc
命令 - 可以在当前工程目录中使用
npx lessc
运行该命令 - 如果配置了
package.json
脚本,无须使用npx
- 会把
{
"name": "practice1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"lessc": "lessc index.less index.css"
},
"author": "",
"license": "ISC",
"devDependencies": {
"less": "^4.1.3"
}
}
npm run lessc
相关信息
- npx 是 npm 提供的一个小工具
- 可以运行当前项目中安装到 node_modules 的 cli 命令
- 应该尽量使用本地安装,而非全局安装
lessc
命令
3)使用 - 对编写的
less
文件进行编译
# 将 index.less 编译成为 index.css
lessc index.less index.css
index.css
4)新建一个页面,引用编译结果 - 每次编写后,都需要运行命令进行编译
3.Less 的核心语法
Less 官网:https://lesscss.org/
Less 民间中文网:https://less.bootcss.com/
提示
- Less 是一种 CSS 预编译器
- 类似的工具还有:less、sass、stylus
(六)使用 nvm 切换 node 版本
- nvm 全称为 node version manger
- 管理 node 版本的一个工具
- 可以在一台计算机上安装多个版本的 node,并且随时进行无缝的切换
1.Mac 系统下安装 nvm
1)卸载原本的 node.js(重要)
2)下载安装 nvm
nvm github 地址:https://github.com/nvm-sh/nvm
- 可以直接下载压缩包,解压后将整个文件夹命名为
.nvm
- 在 Mac 要查看隐藏文件,可以通过
Shift + Command + .
- 在 Mac 要查看隐藏文件,可以通过
- 放入根目录($HOME)下
- 终端工具是 zsh
- 输入
vi ~/.zshrc
- 打开 zsh 终端的配置文件
- 添加如下的代码对 nvm 进行配置
输入
vi ~/.zshrc
命令之后,会打开 zsh 终端的配置文件输入
i
进入 insert 编辑模式,可以进行编辑操作编辑完成之后先按 ESC 退出编辑模式,然后输入
:wq
保存刚才的编辑并退出 - 这里的配置主要包含两个方面,一个是 nvm 路径的配置,另一个是镜像的配置
- 输入
- 终端工具是 bash
- 执行
vi ~/.bashrc
- 打开 bash 终端的配置文件
- 添加如下的配置代码
- 执行
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
export NVM_NODEJS_ORG_MIRROR=http://npm.taobao.org/mirrors/node # 修改镜像
export NVM_IOJS_ORG_MIRROR=http://npm.taobao.org/mirrors/iojs
- 配置完成后,通过命令
source ~/.zshrc
或者source ~/.bashrc
使配置文件生效
3)nvm 常用命令
# 查看当前安装和使用的 node 版本
nvm list
# 安装某个 node 版本
nvm install 版本号
# 切换 node 版本
nvm use 版本号
# 设置默认版本
nvm alias default v12.22.12
4)配置 npm 源
- 安装 node 之后,一般对应的 npm 也会被安装好
- 默认 npm 的源是指向 npm 官网的,导致在下载包的时候会很慢
- 需要修改 npm 的源
npm config set registry=https://registry.npm.taobao.org
npm config get registry
2.Windows 系统下安装 nvm
1)卸载原本的 node.js(重要)
2)下载安装 nvm
链接:https://pan.baidu.com/s/1uoxlk8CVNHV2KTCwIGbQMQ?pwd=yi5m
提取码:yi5m
3)修改 nvm 源
- 直接用 nvm 命令下载 node,因为源在国外,所以会导致下载失败,最好修改 nvm 的源
- 打开 nvm 的下载路径
- 如果是一路 next 的,那么一般就在:
C:\Users\你现在用的用户名\AppData\Roaming\nvm
- 如果是一路 next 的,那么一般就在:
- 打开
setting.txt
文件,在末尾写入:
node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
4)nvm 常用命令
# 查看当前安装和使用的 node 版本
nvm list
# 安装某个 node 版本
nvm install 版本号
# 切换 node 版本
nvm use 版本号
# 设置默认版本
nvm alias v12.22.12
5)配置 npm 源
- 安装 node 之后,一般对应的 npm 也会被安装好
- 默认 npm 的源是指向 npm 官网的,导致在下载包的时候会很慢
- 需要修改 npm 的源
npm config set registry=https://registry.npm.taobao.org
npm config get registry
(七)构建工具的使用
- 工程化,为复杂应用而生
1.核心
- webpack 是用来搭建前端工程的
- 运行在 node 环境中
- 所做的事情,简单来说,就是 打包
- 具体来说,就是以某个模块作为入口,根据入口分析出所有模块的依赖关系
- 然后对各种模块进行合并、压缩,形成最终的打包结果
提示
在 webpack 的世界中,一切皆是模块
2.体验
- 工程以
src/main.js
作为入口文件 - 按照习惯,所有的模块均放置在
src
目录中
1)安装依赖
2)编写多个模块
- 随意编写一些模块,可以是 js、图片、音视频
- 以入口模块为起点,形成依赖关系
npm run build
命令进行打包
3)运行 4)查看打包结果
- 打包结果放置在
dist
目录中
5)好处
- 可以大胆的使用任意模块化标准
- 无须担心兼容性问题
- 因为 webpack 完成打包后,已经没有了任何模块化语句
- 可以将一些非 JS 代码也视为模块
- 可以对 css、图片等资源进行更加细粒度的划分
- 在前端开发中,也可以使用 npm
- webpack 不会运行源代码
- 无论是自己写的模块,还是通过 npm 安装的模块
- webpack 一视同仁,统统视为依赖,最终合并到打包结果中
- 非常适合开发单页应用
- 单页应用是前端用户体验最好的 web 应用
- 所谓单页应用,是指只有一个 html 页面,页面中没有任何内容,所有的内容均靠 js 生成
- 要优雅的实现单页应用,最好依托于前端框架,比如 vue、react
3.页面模板
- 对于单页应用而言,只有一个空白的页面,所有内容都靠 JS 代码创建
- webpack 会自动生成一个页面,并且在页面中会自动加入对 js 和 css 的引用
- 生成页面时,参考的是
public/index.html
,称之为页面模板
4.public 目录
- webpack 会非常暴力的将 public 目录中的所有文件(除页面模板外),复制到打包结果中
5.开发服务器
- 如果每次修改完代码,都要经过
打包 -> 运行
,未免太过麻烦 - 在开发阶段,我们可以运行
npm run serve
命令获得更好的打包体验 - 该命令会让
webpack
启动一个 开发服务器 - 在这个阶段,webpack 并不会形成打包结果文件
- 而是把打包的内容放到内存中
- 当请求服务器时,服务器从内存中给予打包结果
- 与此同时,当源码发生变动时,webpack 会自动重新打包,同时刷新页面以访问到最新的打包结果
6.文件缓存
- 除了页面外,其他的资源在打包完成后,文件名多了一些字符
- 例如:
js/app-9ea93.js
9ea93
这样的字符称为hash
- 会随着模块内容的变化而变化
警告
- 源码内容不变,hash 不变
- 源码内容变化,hash 变化
- 之所以这样做,是因为生产环境中,浏览器会对除页面外的静态资源进行缓存
- 如果不设置 hash 值,一旦代码更新,浏览器还会使用之前缓存的结果,无法使用最新的代码
- 有了 hash 值之后,即可解决此问题
- webpack 会在打包时自动处理 hash 值
7.资源路径
- 除代码和样式模块外,其他模块被视为 资源模块
- 资源模块在源代码中的路径和打包后的路径是 不一样 的
- 这就导致在编写代码的时候,根本无法知晓最终的路径
- 最常见的例子,就是在 css 中使用背景图片
.container {
/* 背景图使用了源码中的路径 */
backgroud: url("../assets/1.png");
}
- 能正常工作
- 因为 webpack 非常智能的发现了这一点,对于 css 中的路径,webpack 在打包时,会将其自动转换为打包结果的路径
- 比如,上面的代码在打包完成后,可能被转换为下面的格式
.container {
/* css中的资源路径会被自动替换,无须关心 */
background: url(/img/1492ea.png);
}
- 如果要通过 js 动态的使用路径,webpack 是无法识别的
// 打包前
const url = "./assets/1.png"; // 该路径无法被转换
img.src = url;
// 打包后
const url = "./assets/1.png"; // ❌
img.src = url;
- 正确的做法是,通过模块化的方式导入资源,并获取资源路径
// 打包前
import url from "./assets/1.png"; // 打包后,url得到的将是真实的路径
img.src = url;
// 打包后
const url = "/img/1492ea.png"; // ✅
img.src = url;
8.缺省的文件和后缀名
- 导入模块时,所有 js 模块均可省略
.js
- 若导入的模块文件名为
index.js
,可省略文件名
import "./home"; // 若存在home.js,可省略js
import "./movie"; // 若movie是一个目录,此次导入的是 ./movie/index.js
9.路径别名
- 随着体量的增长,不可避免的,会形成层级极深的目录
root
|-- src
|-- a
|-- a1
|-- a2
|-- index.js
|-- b
|-- b1
|-- index.js
- 如果需要在
./src/a/a1/a2/index.js
中导入./src/b/b1/index.js
,则可能产生下面特别恶心的代码
import "../../../b/b1/index.js";
- webpack 提供了别名供我们快速定位到
./src
目录 - 通常,该别名为
@
import "@/b/b1"; // @表示src目录,同时省略了index.js
10.js 兼容性
- 当 webpack 读取到 js 代码时,会自动对其进行兼容性处理
- 具体的处理方案涉及到两个配置文件
babel.config.js
1)- 通过配置该文件,可以设置对哪些 js 代码进行降级处理
.browserslistrc
2)- 通过配置该文件,可以设置在降级时,要兼容哪些浏览器
- 兼容的范围越广,降级产生的代码就越多,打包后的体积就越大
11.打包压缩
- webpack 在打包时,会对所有 js 和 css 代码进行压缩
- 对于 js,除了压缩之外,还会对其中的各种名称进行混淆
(self.webpackChunkmovie_list=self.webpackChunkmovie_list||[]).push([[587],{3587:(r,t,n)=>{"use strict";n.r(t),n(5666),n(1539),n(8674),n(9600),n(1249),n(2222);var e=n(9755),a=n.n(e);var o;function i(r){o.html(r.map((function(r){return'<li>\n <a href="'.concat(r.url,'" target="_blank">\n <img src="').concat(r.cover,'" title="').concat(r.title,'">\n </a>\n <a href="').concat(r.url,'" target="_blank" class="').concat("qmUYQv1xlJhGMQKz-kfAp",'">').concat(r.title,'</a>\n <p class="').concat("_3yV5wC-URYTUP0sPvaE0ZR",'">').concat(r.rate,"</p>\n </li>")})).join(""))}o=a()("<ul>").addClass("_1fsrc5VinfYHBXCF1s58qS").appendTo("#app");var c=n(8138);const u=
- 混淆的作用
- 为了进一步压缩包体积
- 为了让代码更难被其他人理解利用
12.源码地图 source map
- 运行的是 webpack 打包后的结果,而打包后的结果是很难阅读的
- 但这样一来会带来新的问题,如果代码报错,就难以知道到底是哪一行代码有问题
- js 代码打包后都会跟上一个同名的、后缀为
.map
的文件- 该文件就保存了原始代码的内容
- 这个内容人类是看不懂的,但浏览器可以看懂
- 当代码报错时,浏览器会定位到源码地图中的对应代码,而不是展示真实报错的代码
13.css 工程化
- webpack 能够识别 所有 的样式代码
- 包括
css
、less
、sass
、stylus
- 在打包时,会转换成纯正的
css
1)自动厂商前缀
- css 有很多兼容性问题,解决这些兼容性问题的最常见办法,就是加上厂商前缀
/* 兼容性不好的代码 */
.container {
display: flex;
transition: 1s;
}
/* 兼容性好的代码 */
.container {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-transition: 1s;
transition: 1s;
}
- webpack 会根据
.browserlistrc
中指定的浏览器范围 - 按需、自动 加上厂商前缀
2)css module
- 无需担心样式名冲突的问题
- 当样式文件以
xxx.mdoule.xxx
的方式命名时 - webpack 会将该文件当成一个开启了
css module
的文件 - 比如:
index.module.less
、movie.module.css
,都是开启了css module
的文件 - 文件中的所有类名都会被 hash 化
// 源码
.container {
}
.list {
}
.item {
}
// 打包结果,绝无可能重名
._2GFVidHvoHtfgtrdifua24 {
}
._1fsrc5VinfYHBXCF1s58qS {
}
.urPUKUukdS_UTSuWRI5-5 {
}
- 使用类名
// styles 是一个对象,里面映射了源码类名和打包类名的关系
import styles from "./index.module.less";
dom.classList.add(styles.container); // ✅ 属性container中记录的就是container转换后的类名
14.真正的 webpack 没有那么神奇
- 实际上,webpack 没有做这么多事,不能把功劳(怨念)全归结于它
- 只是站在巨人(其他流氓)肩膀上而已
- webpack 通过插件(plugin)和加载器(loader)将这些技术整合在一起
配置文件 | 说明 |
---|---|
.browserslistrc | 表达适配的浏览器范围,会被工程化中的其他技术所使用 |
babel.config.js | babel 的配置文件,做 js 降级处理 |
postcss.config.js | postcss 的配置文件,做 css 代码转换 |
webpack.config.js | webpack 的配置文件,整合其他工程化技术,以及配置打包细节、开发服务器、路径别名等等 |
1).browserslistrc
> 1%
last 3 versions
not dead
2)babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: 3,
},
],
],
};
3)postcss.config.js
module.exports = () => ({
plugins: ["cssnano", "autoprefixer"],
});
4)webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "js/app-[contenthash:5].js",
assetModuleFilename: "assets/[hash:5][ext]",
chunkFilename: "js/chunk-[contenthash:5].js",
publicPath: "/",
},
target: "web",
devtool: "source-map",
devServer: {
port: 8080,
},
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
stats: "errors-only",
module: {
rules: [
{
test: /\.(css|less)$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"],
},
{
test: /\.(mp3|mp4)$/,
type: "asset/resource",
},
{
test: /\.(gif|png|webp|svg|jpg|jpeg|bmp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 1024,
},
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: "babel-loader",
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
}),
new MiniCssExtractPlugin({
filename: "css/[name]-[contenthash:5].css",
}),
new CleanWebpackPlugin(),
new CopyPlugin({
// 应用 复制文件 插件
patterns: [
{
from: path.resolve(__dirname, "public"),
to: "./",
globOptions: {
ignore: ["**/*.html"],
},
},
],
}),
],
};
15.对开发的影响
1)学会访问开发服务器查看效果
2)学会动态获取资源文件路径
import url from "./assets/1.png";
img.src = url;
3)学会省略文件和后缀名
import "./home"; // 若存在home.js,可省略js
import "./movie"; // 若movie是一个目录,此次导入的是 ./movie/index.js
4)学会使用别名简化导入代码
import "@/b/b1"; // 实际导入: src/b/b1/index.js (若b1是目录)
5)学会使用 css module
// styles 是一个对象,里面映射了源码类名和打包类名的关系
import styles from "./index.module.less";
dom.classList.add(styles.container);
(八)案例实操:分页电影列表
效果展示地址:https://study.duyiedu.com/movie
接口地址:https://app.apifox.com/link/project/2429576/apis/api-67925177
1.功能模块划分
2.分包
- 如果站点中的所有依赖都打包到一个 js 文件中,势必会导致打包结果过大
- 实际上,在页面初始的时候,不需要那么多代码参与运行
- 比如在这个项目中,一开始必须要运行的只有封面模块
- 因为是用户一开始就必须要能够看见的
- 而电影模块可以慢慢加载
- 可以使用动态导入的方式加载电影模块
// main.js
// 静态导入,表示初始就必须要依赖 cover 模块
import "./cover";
// 动态导入,表示运行到此代码时才会去远程加载 movie 模块
import("./movie");
- webpack 能够识别动态导入的代码
- 当发现某个模块是使用动态导入时,该模块会单独形成打包结果
- 在浏览器运行时,会首先加载初始的打包结果
- 然后在后续的运行过程中,动态加载其他模块
- 这样可以尽量提升初始加载效率,又不影响后续模块的加载
3.跨域代理
- 大部分时候,为了安全,服务器都是不允许跨域访问的
- 所以,将来部署应用的时候,通常会使用下面的方式进行部署
- 最终部署之后,不存在跨域问题
- 跨域问题在开发阶段是存在的
- 要消除开发阶段的跨域问题,便于在开发阶段查看效果
webpack.config.js
中设置代理
1)在 devServer: {
proxy: {
'/api': { // 当请求地址以 api 开头时,代理到另一个地址
target: 'http://study.duyiedu.com', // 代理的目标地址
changeOrigin: true, // 更改请求头中的host,无须深究,为避免出问题,最好写上
},
},
},
ajax
请求时加上请求路径
2)在 axios.get('http://study.duyiedu.com/api/movies'); // ❌ 无须指定源
axios.get('/api/movies'); // ✅
- 这样一来,在跨域问题上,就做到了开发环境和生产环境的统一
4.模块
1)list 模块
/**
* 初始化函数,负责创建容器
*/
function init() {}
init();
/**
* 根据传入的电影数组,创建元素,填充到容器中
* @params movies 电影数组
*/
export function createMovieTags(movies) {}
2)pager 模块
/**
* 初始化函数,负责创建容器
*/
function init() {}
init();
/**
* 根据传入的页码、页容量、总记录数,创建分页区域的标签
* @params page 页码
* @params limit 页容量
* @params total 总页数
*/
export function createPagers(page, limit, total) {}
3)createPagers
/**
* 根据传入的页码、页容量、总记录数,创建分页区域的标签
* @params page 页码
* @params limit 页容量
* @params total 总页数
*/
export function createPagers(page, limit, total) {
/**
* 辅助函数,负责帮忙创建一个页码标签
* @params text 标签的文本
* @params status 标签的状态,空字符串-普通状态,disabled-禁用状态,active-选中状态
*/
function createTag(text, status, targetPage) {}
//1. 创建首页标签
//2. 创建上一页标签
//3. 创建数字页码标签
//4. 创建下一页标签
//5. 创建尾页标签
}