一、工程化

郁子大约 23 分钟约 6964 字笔记渡一教育中枢课袁进

(一)模块化

1.为什么需要模块化

  • 当前端工程到达一定规模后,就会出现以下问题
    • 全局变量污染
    • 依赖混乱
    • 共同导致了 代码文件难以细分
  • 模块化就是为了解决上面两个问题出现的
  • 可以把臃肿的代码细分到各个小文件中
    • 便于后期维护管理

2.前端模块化标准

1)CommonJS

  • 简称 CMJ
  • 是一个 社区 规范
  • 出现时间较早,目前仅 node 环境支持

2)ES Module

  • 简称 ESM
  • 随着 ES6 发布的 官方 模块化标准
  • 目前浏览器和新版本 node 环境(>12.0)均支持

3)node 环境

下载地址:https://nodejs.org/zh-cn/open in new window

(二)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/open in new window


npm 全命令:https://docs.npmjs.com/cli/v7/commandsopen in new window

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

1)新建 index.less 文件

@green: #008c8c;
.list {
  display: flex;
  flex-wrap: wrap;
  color: @green;
  li {
    margin: 1em;
    &:hover {
      background: @green;
      color: #fff;
    }
  }
}

2)使用 npm 下载 less

  • 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 命令
  • 应该尽量使用本地安装,而非全局安装

3)使用 lessc 命令

  • 对编写的 less 文件进行编译
# 将 index.less 编译成为 index.css
lessc index.less index.css

4)新建一个页面,引用编译结果 index.css

  • 每次编写后,都需要运行命令进行编译

3.Less 的核心语法

Less 官网:https://lesscss.org/open in new window


Less 民间中文网:https://less.bootcss.com/open in new window

提示

  • 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/nvmopen in new window

  • 可以直接下载压缩包,解压后将整个文件夹命名为 .nvm
    • 在 Mac 要查看隐藏文件,可以通过 Shift + Command + .
  • 放入根目录($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=yi5mopen in new window


提取码:yi5m

3)修改 nvm 源

  • 直接用 nvm 命令下载 node,因为源在国外,所以会导致下载失败,最好修改 nvm 的源
  • 打开 nvm 的下载路径
    • 如果是一路 next 的,那么一般就在:C:\Users\你现在用的用户名\AppData\Roaming\nvm
  • 打开 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、图片、音视频
  • 以入口模块为起点,形成依赖关系

3)运行 npm run build 命令进行打包

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 代码时,会自动对其进行兼容性处理
  • 具体的处理方案涉及到两个配置文件

1)babel.config.js

  • 通过配置该文件,可以设置对哪些 js 代码进行降级处理

2).browserslistrc

  • 通过配置该文件,可以设置在降级时,要兼容哪些浏览器
  • 兼容的范围越广,降级产生的代码就越多,打包后的体积就越大

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 能够识别 所有 的样式代码
  • 包括 csslesssassstylus
  • 在打包时,会转换成纯正的 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.lessmovie.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.jsbabel 的配置文件,做 js 降级处理
postcss.config.jspostcss 的配置文件,做 css 代码转换
webpack.config.jswebpack 的配置文件,整合其他工程化技术,以及配置打包细节、开发服务器、路径别名等等

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/movieopen in new window


接口地址:https://app.apifox.com/link/project/2429576/apis/api-67925177open in new window

1.功能模块划分

2.分包

  • 如果站点中的所有依赖都打包到一个 js 文件中,势必会导致打包结果过大

  • 实际上,在页面初始的时候,不需要那么多代码参与运行
  • 比如在这个项目中,一开始必须要运行的只有封面模块
    • 因为是用户一开始就必须要能够看见的
    • 而电影模块可以慢慢加载
  • 可以使用动态导入的方式加载电影模块
// main.js

// 静态导入,表示初始就必须要依赖 cover 模块
import "./cover";

// 动态导入,表示运行到此代码时才会去远程加载 movie 模块
import("./movie");
  • webpack 能够识别动态导入的代码
  • 当发现某个模块是使用动态导入时,该模块会单独形成打包结果

  • 在浏览器运行时,会首先加载初始的打包结果
  • 然后在后续的运行过程中,动态加载其他模块
  • 这样可以尽量提升初始加载效率,又不影响后续模块的加载

3.跨域代理

  • 大部分时候,为了安全,服务器都是不允许跨域访问的
  • 所以,将来部署应用的时候,通常会使用下面的方式进行部署
    • 最终部署之后,不存在跨域问题

  • 跨域问题在开发阶段是存在的
  • 要消除开发阶段的跨域问题,便于在开发阶段查看效果

1)在 webpack.config.js 中设置代理

devServer: {
  proxy: {
    '/api': { // 当请求地址以 api 开头时,代理到另一个地址
      target: 'http://study.duyiedu.com', // 代理的目标地址
      changeOrigin: true, // 更改请求头中的host,无须深究,为避免出问题,最好写上
    },
  },
},

2)在 ajax 请求时加上请求路径

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. 创建尾页标签
}
上次编辑于: