Skip to content
001

02_模块 + npm 与 包 + 发布包 + 模块的加载机制

01_模块

01_模块的概念

01_什么是模块

  • 模块 是指解决一个 复杂问题 时, 自顶向下逐层 把系统划分成若干模块的过程
  • 对于整个系统来说, 模块是可组合, 分解和更换的单元

现实生活中的模块

002

编程领域中的模块

  • 编程领域中的模块, 就是 遵守固定的规则, 把一个 大文件 拆成 独立并互相依赖多个小模块

把代码进行模块拆分的好处:

  1. 提高了代码的 复用性
  2. 提高了代码的 可维护性
  3. 可以实现 按需加载

02_模块化规范

模块化规范 就是对代码进行模块化的拆分与组合时, 需要遵守的那些规则

例如:

  • 使用什么样的语法格式来 引用模块
  • 在模块中使用什么样的语法格式 向外暴露成员

模块规范的好处: 大家都遵守同样的模块化规范写代码, 降低了沟通的成本, 极大方便了各个模块之间的相互调用, 利人利己


02_模块的分类 + 加载模块

01_Node 中模块的分类

Node.js 中根据模块来源的不同, 将模块分为了 3 大类, 分别是:

  • 内置模块 (内置模块是由 Node.js 官方提供的, 例如 fs, path, http 等)
  • 自定义模块 (用户创建的每个 .js 文件, 都是自定义模块)
  • 第三方模块 (由第三方开发出来的模块, 并非官方提供的内置模块, 也不是用户创建的自定义模块, 使用前需要先下载)

02_加载模块

使用强大的 require() 方法, 可以加载需要的 内置模块, 用户自定义模块, 第三方模块 进行使用, 例如:

003

注意: 使用 require() 方法加载其它模块时, 会执行被加载模块中的代码

javascript
// /code/mokuai.js

// 1.当前这个文件, 就是一个用户自定义模块
console.log('加载了 mokuai 这个用户自定义模块')
javascript
// /code/test.js

// 2.在使用 require 加载用户自定义模块期间, 可以省略 .js 的后缀名
const m = require('./mokuai.js')
console.log(m)

03_模块的作用域 + module 对象

01_什么是模块的作用域

  • 函数作用域 类似, 在自定义模块中定义的 变量, 方法 等成员, 只能在当前模块内被访问, 这种 模块级别的访问限制, 叫做 模块作用域
004

模块作用域的好处

  • 防止了全局变量污染的问题
005
javascript
// /code/mokuai.js

const username = '张三'

function sayHello() {
  console.log('大家好, 我是' + username)
}
javascript
// /code/test.js

// 1.在自定义模块中定义的 变量, 方法 等成员, 只能在当前模块内被访问
const custom = require('./mokuai.js')
console.log(custom) // {}

02_module 对象

  • 在每个 .js 自定义模块中都有一个 module 对象, 它里面 存储了和当前模块有关的信息, 打印如下:
006
javascript
// /code/index.js

console.log(module)

04_module.exports 对象

01_module.exports 对象

  • 在自定义模块中, 可以使用 module.exports 对象, 将模块内的成员共享出去, 供外界使用
  • 外界用 require() 方法 导入自定义模块时, 得到的就是 module.exports 所指向的对象
javascript
// /code/mokuai.js

// 1.在一个自定义模块中, 默认情况下, module.exports = {}

// 2.向 module.exports 对象上挂载 username 属性
module.exports.username = 'zs'

// 3.向 module.exports 对象上挂载 sayHello 方法
module.exports.sayHello = function() {
  console.log('Hello!')
}

// 4.向 module.exports 对象上挂载 age 属性
const age = 20
module.exports.age = age
javascript
// /code/test.js

// 5.外界用 require() 方法 导入自定义模块时, 得到的就是 module.exports 所指向的对象
const m = require('./mokuai.js')

console.log(m)

02_共享成员时的注意点

使用 require() 方法导入横块时, 导入的结果, 永远以 module.exports 指向的对象为准

007
javascript
// /code/mokuai.js

// 在一个自定义模块中, 默认情况下,  module.exports = {}

// 向 module.exports 对象上挂载 username 属性
module.exports.username = 'zs'

// 向 module.exports 对象上挂载 sayHello 方法
module.exports.sayHello = function() {
  console.log('Hello!')
}

// 向 module.exports 对象上挂载 age 属性
const age = 20
module.exports.age = age

// 1.让 module.exports 指向一个全新的对象
module.exports = {
  nickname: '小黑',
  sayHi() {
    console.log('Hi!')
  }
}

05_exports 对象

01_exports 对象

  • 由于 module.exports 单词写起来比较复杂, 为了简化向外共享成员的代码, Node 提供了 exports 对象
  • 默认情况下, exports 和 module.exports 指向同一个对象
  • 最终共享的结果, 还是以 module.exports 指向的对象为准
008
javascript
// /code/mokuai.js

// 1.exports 和 module.exports 指向同一个对象
console.log(exports) // {}
console.log(module.exports) // {}
console.log(exports === module.exports) // true

// 2.最终, 向外共享的结果, 永远都是 module.exports 所指向的对象
const username = 'zs'
module.exports.username = username
exports.age = 20
exports.sayHello = function() {
  console.log('大家好!')
}
javascript
// /code/test.js

// 外界用 require() 方法 导入自定义模块时, 得到的就是 module.exports 所指向的对象
const m = require('./mokuai.js')

console.log(m)

06_使用 exports 与 module.exports 的误区

01_使用 exports 与 module.exports 的误区

  • 时刻谨记, require() 模块时, 得到的永远是 module.exports 指向的对象:
009010011012

注意: 为了防止混乱, 建议大家不要在同一个模块中同时使用 exports 和 module.exports


07_CommonJS 模块的规范

01_Node 中模块的规范

Node.js 遵循了 CommonJS 模块的规范, CommonJS 规定了 模块的特性各模块之间如何相互依赖

CommonJS 规定:

  1. 每个模块内部, module 变量 代表当前模块
  2. module 变量是一个对象, 它的 exports 属性 (即 module.exports) 是对外的接口
  3. 加载某个模块, 其实是加载该模块的 module.exports 属性, require() 方法用于加载模块

02_npm 与 包

01_包的介绍 + npm 下载包

01_什么是包

  • Node.js 中的 第三方模块 又叫做
  • 就像 电脑计算机 指的是相同的东西, 第三方模块 指的是同一个概念, 只不过叫法不同

02_包的来源

  • 不同于 Node.js 中的内置模块与自定义模块, 包是由第三方个人或团队开发出来的, 免费供所有人使用
  • 注意: Node.js 中的包都是免费且开源的, 不需要付费即可免费下载使用

03_为什么需要包

  • 由于 Node.js 的内置模块仅提供了一些底层的 API, 导致在基于内置模块进行项目开发的时, 效率很低
  • 包是基于内置模块封装出来的, 提供了更高级, 更方便的 API, 极大的提高了开发效率
  • 内置模块 之间的关系, 类似于 jQuery浏览器内置 API 之间的关系

04_从哪里下载包

  • 国外有一家 IT 公司, 叫做 npm, Inc. 这家公司旗下有一个非常著名的网站: https://www.npmjs.com/, 它是 全球最大的包共享平台, 你可以从这个网站上搜索到任何你需要的包, 只要你有足够的耐心!
  • 到目前位置, 全球约 1100 多万 的开发人员, 通过这个包共享平台, 开发并共享了超过 120 多万个包 供我们使用
  • npm, Inc.公司 提供了一个地址为 https://registry.npmjs.org/ 的服务器, 来对外共享所有的包, 我们可以从这个服务器上下载自己所需要的包

注意:


05_如何下载包

  • npm, Inc.公司 提供了一个包管理工具, 我们可以使用这个包管理工具, 从 https://registry.npmjs.org/ 服务器把需要的包下载到本地使用
  • 这个包管理工具的名字叫做 Node Package Manager (简称 npm 包管理工具), 这个包管理工具随着 Node.js 的安装包一起被安装到了用户的电脑上

大家可以在终端中执行 npm -v 命令, 来查看自己电脑上所安装的 npm 包管理工具的版本号:

013

02_格式化时间的包

01_格式化时间的传统做法

  1. 创建格式化时间的自定义模块
  2. 定义格式化时间的方法
  3. 创建补零函数
  4. 从自定义模块中导出格式化时间的函数
  5. 导入格式化时间的自定义模块
  6. 调用格式化时间的函数
014
javascript
// /code/dateFormat.js

// 1.定义格式化时间的方法
function dateFormat(dtStr) {
  const dt = new Date(dtStr)

  const y = dt.getFullYear()
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 2.定义补零的函数
function padZero(n) {
  return n > 9 ? n : '0' + n
}

module.exports = {
  dateFormat
}
javascript
// /code/test.js

// 3.导入自定义的格式化时间的模块
const TIME = require('./dateFormat.js')

// 4.调用方法, 进行时间的格式化
const dt = new Date()
console.log(dt) // 2025-01-02T11:30:26.077Z

const newDT = TIME.dateFormat(dt)
console.log(newDT) // 2025-01-02 11:30:26

02_格式化时间的高级做法

  1. 使用 npm 包管理工具, 在项目中安装格式化时间的包 moment
  2. 使用 require() 导入格式化时间的包
  3. 参考 moment 的官方 API 文档对时间进行格式化
015

03_在项目中安装包的命令

如果想在项目中安装指定名称的包, 需要运行如下的命令:

016

上述的装包命令, 可以简写成如下格式:

017
bash
# 格式化日期和时间
PS C:\Users\18123\Desktop\code> npm i moment@2.24.0

added 1 package in 4s
javascript
// /code/test.js

// 1.导入需要的包; 注意:导入的名称, 就是装包时候的名称
const moment = require('moment')

// 2.调用 moment 方法, 得到当前时间; format 方法, 按照指定格式, 把时间格式化
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt) // 2025-01-02 20:15:55

03_包的版本

01_初次装包后多了哪些文件

初次装包完成后, 在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件

其中:

  • node_modules 文件夹 用来 存放所有已安装到项目中的包; require() 导入第三方包时, 就是从这个目录中查找并加载包
  • package-lock.json 配置文件 用来 记录 node_modules 目录下的每一个包的下载信息, 例如包的名字, 版本号, 下载地址等

注意: 程序员不要手动修改 node_modules 或 package-lock.json 文件中的任何代码, npm 包管理工具会自动维护它们


02_安装指定版本的包

默认情况下, 使用 npm install 命令安装包的时候, 会自动安装最新版本的包

如果需要安装指定版本的包, 可以在包名之后, 通过 @ 符号 指定具体的版本, 例如:

018

03_包的语义化版本规范

包的版本号是以 "点分十进制" 形式进行定义的, 总共有三位数字, 例如 2.24.0

其中每一位数字所代表的的含义如下:

  • 第 1 位数字: 大版本
  • 第 2 位数字: 功能版本
  • 第 3 位数字: Bug 修复版本

版本号提升的规则: 只要前面的版本号增长了, 则后面的版本号 归零


04_包的配置文件

01_包管理配置文件

npm 规定, 在 项目根目录 中, 必须 提供一个叫做 package.json 的包管理配置文件

用来记录与项目有关的一些配置信息, 例如:

  • 项目的名称, 版本号, 描述等
  • 项目中都用到了哪些包
  • 哪些包只在 开发期间 会用到
  • 那些包在 开发部署 时都需要用到

02_多人协作的问题

遇到的问题: 第三方包的体积过大, 不方便团队成员之间共享项目源代码

  • 整个项目的体积是 30.4M
  • 第三方包的体积是 28.8M
  • 项目源代码的体积 1.6M

解决方案: 共享时剔除 node_modules

019

03_如何记录项目中安装了那些包

项目根目录 中, 创建一个叫做 package.json 的配置文件, 即可用来记录项目中安装了哪些包

从而方便剔除 node_modules 目录之后, 在团队成员之间共享项目的源代码

注意: 今后在项目开发中, 一定要把 node_modules 文件夹, 添加到 .gitignore 忽略文件中


04_快速创建 package.json

npm 包管理工具提供了一个 快捷命令, 可以在 执行命令时所处的目录中, 快速创建 package.json 这个包管理, 配置文件:

020

注意:

  1. 上述命令 只能在英文的目录下成功运行! 所以, 项目文件夹的名称定要使用英文命名, 不要使用中文, 不能出现空格
  2. 运行 npm install 命令安装包的时候, npm 包管理工具会自动把 包的名称版本号, 记录到 package.json 中
bash
# 快速新建 package.json 文件
PS C:\Users\18123\Desktop\code> npm init -y
Wrote to C:\Users\18123\Desktop\code\package.json:       

{
  "name": "code",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

05_dependencies 节点

package.json 文件中, 有一个 dependencies 节点, 专门用来记录您使用 npm instal 命令安装了哪些包

021
bash
# 格式化日期和时间
PS C:\Users\18123\Desktop\code> npm i moment@2.24.0

added 1 package in 4s

# 方便地操作 DOM 元素, 处理事件; art-template 模板引擎
PS C:\Users\18123\Desktop\code> npm i jquery@3.4.1 art-template@4.13.2

added 34 packages in 9s

06_一次性安装所有的包

当我们拿到一个 剔除了 node_modules 的项目之后, 需要先把所有的包下载到项目中, 才能将项目运行起来

否则会报类似于下面的错误:

022

可以运行 npm install 命令 (或 npm i) 一次性安装所有的依赖包:

023
javascript
// /code/dateFormat.js

const moment = require('moment')

// 调用 moment 方法, 得到当前时间; format 方法, 按照指定格式, 把时间格式化
const dt = moment().format('YYYY-MM-DD')
console.log(dt) // 2025-01-02
bash
# 1.一次性安装所有的包
PS C:\Users\18123\Desktop\code> npm i

added 35 packages in 6s

07_卸载包

可以运行 npm uninstall 命令, 来卸载指定的包:

024

注意: npm uninstall 命令执行成功后, 会把卸载的包, 自动从 padckage.json 的 dependencies 中移除掉

bash
# 1.使用 npm uninstall 具体的包名 来卸载包
PS C:\Users\18123\Desktop\code> npm uninstall moment

removed 1 package in 1s

08_devDependencies 节点

如果某些包 只在项目开发阶段 会用到, 在 项目上线之后不会用到, 则建议把这些包记录到 devDependencies 节点中

与之对应的, 如果某些包在 开发项目上线之后 都需要用到, 则建议把这些包记录到 dependencies 节点中

您可以使用如下的命令, 将包记录到 devDependencies 节点中:

025
bash
# 某些包 只在项目开发阶段 会用到
PS C:\Users\18123\Desktop\code> npm i webpack@4.42.1 -D
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is 
much more comprehensive and powerful.
npm warn deprecated move-concurrently@1.0.1: This package is no longer supported.
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated copy-concurrently@1.0.5: This package is no longer supported.
npm warn deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm warn deprecated figgy-pudding@3.5.2: This module is no longer supported.
npm warn deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm warn deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm warn deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated fs-write-stream-atomic@1.0.10: This package is no longer supported.

added 355 packages in 1m

30 packages are looking for funding
  run `npm fund` for details

05_npm 的镜像

01_为什么下包速度慢

  • 在使用 npm 下包的时候, 默认从国外的 https://registry.npmjs.org/ 服务器进行下载, 此时, 网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢

02_淘宝 NPM 镜像服务器

淘宝在国内搭建了一个服务器, 专门把国外官方服务器上的包 同步 到国内的服务器, 然后在国内提供下包的服务

从而极大的提高了下包的速度

026

扩展: 镜像 (Mirroring) 是一种文件存储形式, 一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像

027

03_切换 npm 的下包镜像源

下包的镜像源, 指的就是 下包的服务器地址

028
bash
# 查看当前的下包镜像源
PS C:\Users\18123\Desktop> npm config get registry
https://registry.npmjs.org

# 将下包的镜像源切换为淘宝镜像源
PS C:\Users\18123\Desktop> npm config set registry=https://registry.npmmirror.com/

# 检查镜像源是否下载成功
PS C:\Users\18123\Desktop> npm config get registry
https://registry.npmmirror.com/

04_nrm 查看切换镜像

为了更方便的切换下包的镜像源, 我们可以安装 nrm 这个小工具, 利用 nrm 提供的终端命令, 可以快速查看和切换下包的镜像源

029
bash
# 通过 npm 包管理器, 将 nrm 安装为全局可用的工具
C:\Users\18123\Desktop>npm i nrm -g

changed 34 packages in 4s

8 packages are looking for funding
  run `npm fund` for details

# 查看所有可用的镜像源
C:\Users\18123\Desktop>nrm ls
  npm ---------- https://registry.npmjs.org/
  yarn --------- https://registry.yarnpkg.com/
  tencent ------ https://mirrors.tencent.com/npm/
  cnpm --------- https://r.cnpmjs.org/
* taobao ------- https://registry.npmmirror.com/
  npmMirror ---- https://skimdb.npmjs.com/registry/
  huawei ------- https://repo.huaweicloud.com/repository/npm/

# 将下包的镜像源切换为 taobao 镜像
C:\Users\18123\Desktop>nrm use taobao
 SUCCESS  The registry has been changed to 'taobao'.

06_包的分类 + 包的结构

01_项目包

那些被安装到 项目node_modules 目录 中的包, 都是项目包

项目包又分为两类, 分别是:

  • 开发依赖包 (被记录到 devDependencies 节点中的包, 只在开发期间会用到)
  • 核心依赖包 (被记录到 dependencies 节点中的包, 在开发期间和项目上线之后都会用到)
030

02_全局包

在执行 npm install 命令时, 如果提供了 -g 参数, 则会把包安装为 全局包

全局包会被安装到 C:\Users\18123\AppData\Roaming\npm\node_modules 目录下

031

注意:

  1. 只有 工具性质的包, 才有全局安装的必要性; 因为它们提供了好用的终端命令
  2. 判断某个包是否需要全局安装后才能使用, 可以 参考官方提供的使用说明 即可

03_i5ting_toc

i5ting_toc 是一个可以把 md 文档转为 html 页面的小工具, 使用步骤如下:

032
bash
# 将 i5ting_toc 安装为全局包
C:\Users\18123\Desktop>npm install -g i5ting_toc@1.1.5

changed 17 packages in 2s

1 package is looking for funding
  run `npm fund` for details

# 调用 i5ting_toc 轻松实现 md 转 html 的功能
PS C:\Users\18123\Desktop\code> i5ting_toc -f .\day1.md -o   
pwd=C:\Users\18123\Desktop\code
source_file_name=C:\Users\18123\Desktop\code/.\day1.md       
dest_file_path=C:\Users\18123\Desktop\code/preview/\day1.html

04_规范的包结构

在清楚了包的概念, 以及如何下载和使用包之后, 接下来, 我们深入了解一下 包的内部结构

一个规范的包, 它的组成结构, 必须符合以下 3 点要求:

  1. 包必须以 单独的目录 而存在
  2. 包的顶级目录下要必须包含 package.json 这个包管理配置文件
  3. package.json 中必须包含 name, version, main 这三个属性, 分别代表 包的名字, 版本号, 包的入口

03_发布包

01_初始化包的基本结构

01_需要实现的功能

  1. 格式化日期
  2. 转义 HTML 中的 特殊字符
  3. 还原 HTML 中的 特殊字符
033034035

02_初始化包的基本结构

  1. 新建 itheima-tools 文件夹, 作为 包的根目录
  2. 在 itheima-tools 文件夹中, 新建如下三个文件:
    1. package.json (包管理配置文件)
    2. index.js (包的入口文件)
    3. README.md (包的说明文档)

03_初始化 package.json

036
json
// /code/itheima-tools/package.json

{
  "name": "itheima-tools",
  "version": "1.1.0",
  "main": "index.js",
  "description": "提供了格式化时间, HTMLEscape 相关的功能",
  "keywords": [
    "itheima",
    "dateFormat",
    "escape"
  ],
  "license": "ISC"
}

02_格式化时间

01_在 index.js 中定义格式化时间的方法

037
javascript
// /code/itheima-tools/index.js

// 这是包的入口文件

// 1.定义格式化时间的函数
function dateFormat(dateStr) {
  const dt = new Date(dateStr)

  const y = dt.getFullYear()
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 2.定义一个补零的函数
function padZero(n) {
  return n > 9 ? n : '0' + n
}

// 3.向外暴露需要的成员
module.exports = {
  dateFormat
}
javascript
// /code/test.js

const itheima = require('./itheima-tools')

// 4.格式化时间的功能
const dtStr = itheima.dateFormat(new Date())
console.log(dtStr)

03_转义 HTML 字符 + 还原 HTML 字符串

01_在 index.js 中定义转义 HTML 的方法

038
javascript
// /code/itheima-tools/index.js

// 这是包的入口文件

// 定义格式化时间的函数
function dateFormat(dateStr) {
  const dt = new Date(dateStr)

  const y = dt.getFullYear()
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 定义一个补零的函数
function padZero(n) {
  return n > 9 ? n : '0' + n
}

// 1.定义转义 HTML 字符的函数
function htmlEscape(htmlstr) {
  return htmlstr.replace(/<|>|"|&/g, match => {
    switch (match) {
      case '<':
        return '&lt;'
      case '>':
        return '&gt;'
      case '"':
        return '&quot;'
      case '&':
        return '&amp;'
    }
  })
}

// 2.向外暴露需要的成员
module.exports = {
  dateFormat,
  htmlEscape
}
javascript
// /code/test.js

const itheima = require('./itheima-tools')

// 3.转义 HTML 字符
const htmlStr = '<h1 title="abc">这是 h1 标签<span>123&nbsp;</span></h1>'
const str1 = itheima.htmlEscape(htmlStr)
console.log(str1)

02_在 index.js 中定义还原 HTML 的方法

039
javascript
// /code/itheima-tools/index.js

// 这是包的入口文件

// 定义格式化时间的函数
function dateFormat(dateStr) {
  const dt = new Date(dateStr)

  const y = dt.getFullYear()
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 定义一个补零的函数
function padZero(n) {
  return n > 9 ? n : '0' + n
}

// 定义转义 HTML 字符的函数
function htmlEscape(htmlstr) {
  return htmlstr.replace(/<|>|"|&/g, match => {
    switch (match) {
      case '<':
        return '&lt;'
      case '>':
        return '&gt;'
      case '"':
        return '&quot;'
      case '&':
        return '&amp;'
    }
  })
}

// 1.定义还原 HTML 字符串的函数
function htmlUnEscape(str) {
  return str.replace(/&lt;|&gt;|&quot;|&amp;/g, match => {
    switch (match) {
      case '&lt;':
        return '<'
      case '&gt;':
        return '>'
      case '&quot;':
        return '"'
      case '&amp;':
        return '&'
    }
  })
}

// 2.向外暴露需要的成员
module.exports = {
  dateFormat,
  htmlEscape,
  htmlUnEscape
}
javascript
// /code/test.js

const itheima = require('./itheima-tools')

// 转义 HTML 字符
const htmlStr = '<h1 title="abc">这是 h1 标签<span>123&nbsp;</span></h1>'
const str1 = itheima.htmlEscape(htmlStr)
console.log(str1)

// 3.还原 HTML 字符串
const str2 = itheima.htmlUnEscape(str1)
console.log(str2)

04_功能进行模块化拆分 + 说明文档

01_将不同的功能进行模块化拆分

  1. 将格式化时间的功能, 拆分到 src ==> dateFormat.js
  2. 将处理 HTML 字符串的功能, 拆分到 src ==> htmlEscape.js
  3. 在 index.js 中, 导入两个模块, 得到需要向外共享的方法
  4. 在 index.js 中, 使用 module.exports 把对应的方法共享出去
javascript
// /code/itheima-tools/src/dateFormat.js

// 定义格式化时间的函数
function dateFormat(dateStr) {
  const dt = new Date(dateStr)

  const y = dt.getFullYear()
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 定义一个补零的函数
function padZero(n) {
  return n > 9 ? n : '0' + n
}

// 向外暴露需要的成员
module.exports = {
  dateFormat
}
javascript
// /code/itheima-tools/src/htmlEscape.js

// 定义转义 HTML 字符的函数
function htmlEscape(htmlstr) {
  return htmlstr.replace(/<|>|"|&/g, match => {
    switch (match) {
      case '<':
        return '&lt;'
      case '>':
        return '&gt;'
      case '"':
        return '&quot;'
      case '&':
        return '&amp;'
    }
  })
}

// 定义还原 HTML 字符串的函数
function htmlUnEscape(str) {
  return str.replace(/&lt;|&gt;|&quot;|&amp;/g, match => {
    switch (match) {
      case '&lt;':
        return '<'
      case '&gt;':
        return '>'
      case '&quot;':
        return '"'
      case '&amp;':
        return '&'
    }
  })
}

// 向外暴露需要的成员
module.exports = {
  htmlEscape,
  htmlUnEscape
}
javascript
// /code/itheima-tools/index.js

// 这是包的入口文件

const date = require('./src/dateFormat')
const escape = require('./src/htmlEscape')

// 向外暴露需要的成员
module.exports = {
  ...date,
  ...escape
}
javascript
// /code/test.js

const itheima = require('./itheima-tools')

// 格式化时间的功能
const dtStr = itheima.dateFormat(new Date())
console.log(dtStr)
console.log('----------------------------------------------------------')

// 转义 HTML 字符
const htmlStr = '<h1 title="abc">这是 h1 标签<span>123&nbsp;</span></h1>'
const str1 = itheima.htmlEscape(htmlStr)
console.log(str1)
console.log('----------------------------------------------------------')

// 还原 HTML 字符串
const str2 = itheima.htmlUnEscape(str1)
console.log(str2)

02_编写包的说明文档

  • 包根目录中的 README.md 文件, 是 包的使用说明文档
  • 通过它, 我们可以事先把包的使用说明, 以 markdown 的格式写出来, 方便用户参考
  • README 文件中與体写什么内容, 没有强制性的要求; 只要能够清晰地把包的作用, 用法, 注意事项等描述清楚即可

我们所创建的这个包的 README.md 文档中, 会包含以下 6 项内容客:

  • 安装方式, 导入方式, 格式化时间, 转义 HTML 中的特殊字符, 还原 HTML 中的特殊字符, 开源协议
bash
## 安装
```
npm install itheima-tools
```

## 导入
```js
const itheima = require('itheima-tools')
```

## 格式化时间
```js
// 调用 dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date())
// 结果  2020-04-03 17:20:58
console.log(dtStr)
```

## 转义 HTML 中的特殊字符
```js
// 带转换的 HTML 字符串
const htmlStr = '<h1 title="abc">这是 h1 标签<span>123&nbsp;</span></h1>'
// 调用 htmlEscape 方法进行转换
const str = itheima.htmlEscape(htmlStr)
// 转换的结果 &lt;h1 title=&quot;abc&quot;&gt;这是h1标签&lt;span&gt;123&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
console.log(str)
```

## 还原 HTML 中的特殊字符
```js
// 待还原的 HTML 字符串
const str2 = itheima.htmlUnEscape(str)
// 输出的结果 <h1 title="abc">这是 h1 标签<span>123&nbsp;</span></h1>
console.log(str2)
```

## 开源协议
ISC

05_把包发布到 npm 上

01_注册 npm 账号

  1. 访问 https://www.npmjs.com/ 网站, 点击 sign up 按钮, 进入注册用户界面
  2. 填写账号相关的信息: Full Name, Public Email, Username, Paassword
  3. 点击 Create an Account 按钮, 注册账号
  4. 登录邮箱, 点击验证链接, 进行账号的验证

02_登录 npm 账号

npm 账号注册完成后, 可以在终端中执行 npm login 命令, 依次输入 用户名, 密码, 邮箱后, 即可登录成功

注意: 在运行 npm login 命令之前, 必须先把 下包的服务器 地址切换为 npm 的官方服务器; 否则会导致发布包失败!

040
bash
# 查看所有可用的镜像源
PS C:\Users\18123\Desktop\code> nrm ls
  npm ---------- https://registry.npmjs.org/
  yarn --------- https://registry.yarnpkg.com/
  tencent ------ https://mirrors.tencent.com/npm/
  cnpm --------- https://r.cnpmjs.org/
* taobao ------- https://registry.npmmirror.com/
  npmMirror ---- https://skimdb.npmjs.com/registry/
  huawei ------- https://repo.huaweicloud.com/repository/npm/

# 将下包的镜像源切换为 npm 镜像
PS C:\Users\18123\Desktop\code> nrm use npm
 SUCCESS  The registry has been changed to 'npm'.

# 登录 npm 账号 (一直登录不了)
PS C:\Users\18123\Desktop\code> npm login
Username: wodeyiyezhiqiu
Password:
Email: (this IS public) front@itcast.cn
Logged in as llb1314 on https://registry.npmjs.org/.

03_把包发布到 npm 上

将终端切换到包的根目录之后, 运行 npm publish 命令, 即可将包为发布到 npm 上 (注意: 包名不能雷同)

041
bash
# 把包发布到 npm 上
PS C:\Users\18123\Desktop\code\itheima-tools> npm publish
npm notice === Tarball Details ===
npm notice name:      itheima-tools
npm notice version:   1.1.0
npm notice package size:  1.3 kB
npm notice unpacked size:  2.6 kB  |
npm notice shasum:    46af58e989e291fdf1ffa53cc1792b5642e82e2
npm notice integrity: sha512-mS0oaPbamHMLs[... ]XN3e8wYMUUaA==
npm notice total files:   5
npm notice
+ itheima-tools@1.1.0

04_删除已发布的包

运行 npm unpublish 包名 --force 命令, 即可从 npm 删除已发布的包

042

注意:

  1. npm unpublish 命令只能删除 72 小时以内 发布的包
  2. npm unpublish 删除的包, 在 24 小时内 不允许重复发布
  3. 发布包的时候要慎重, 尽量不要往 npm 上发布没有意义的包!
bash
# 删除已发布的包
PS C:\Users\18123\Desktop\code\itheima-tools> npm unpublish itheima-tools --force
npm WARN using --force I sure hope you know what you are doing.
- itheima-tools

04_模块的加载机制

01_内置模块 + 自定义模块 + 第三方模块

01_优先从缓存中加载

模块在第一次加载后会被缓存; 这也意味着多次调用 require() 不会导致模块的代码被执行多次

注意: 不论是内置模块, 用户自定义模块, 还是第三方模块, 它们都会优先从缓存中加载, 从而 提高模块的加载效率

javascript
// /code/mokuai.js

console.log('ok')
javascript
// /code/test.js

require('./mokuai.js')
require('./mokuai.js')
require('./mokuai.js')

02_内置模块的加载机制

内置模块是由 Node.js 官方提供的模块, 内置模块的加载优先级最高

例如, require('fs') 始终返回内置的 fs 模块, 即使在 node_modules 目录下有名字相同的包也叫做 fs


03_自定义模块的加载机制

使用 require() 加载自定义模块时, 必须指定以 ./../ 开头的 路径标识符

在加载自定义模块时, 如果没有指定 ./ 或 ../ 这样的路径标识符, 则 node 会把它当作 内置模块第三方模块 进行加载

同时, 在使用 require() 导入自定义模块时, 如果省略了文件的扩展名, 则 Node.js 会 按顺序 分别尝试加载以下的文件:

  1. 按照 确切的文件名 进行加载
  2. 补全 .js 扩展名进行加载
  3. 补全 .json 扩展名进行加载
  4. 补全 .node 扩展名进行加载
  5. 加载失败, 终端报错
// /code/mokuai

console.log('加载了 mokuai 文件')
javascript
// /code/mokuai.js

console.log('加载了 mokuai.js 文件')
json
// /code/mokuai.json

{
  "name": "mokuai.json"
}
javascript
// /code/test.js

const m = require('./mokuai')
console.log(m)

04_第三方模块的加载机制

如果传递给 require() 的模块标识符不是一个内置模块, 也没有以 ./../ 开头, 则 Node.js 会从当前模块的父目录开始, 尝试从 /node_modules 文件夹中加载第三方模块

如果没有找到对应的第三方模块, 则移动到再上一层父目录中, 进行加载, 直到文件系统的根目录

例如, 假设在 'C:\Users\itheima\project\foo.js' 文件里调用了 reequire('tools'), 则 Node.js 会按以下顺序查找:

  1. C:\Users\itheima\project\node_modules\tools
  2. C:\Users\itheima\node_modules\tools
  3. C:\Users\node_modules\tools
  4. C:\node_modules\tool

05_目录作为模块

当把目录作为模块标识符, 传递给 require() 进行加载的时候, 有三种加载方式:

  1. 在被加载的目录下查找一个叫做 package.json 的文件, 并寻找 main 属性, 作为 require() 加载的入口
  2. 如果目录里没有 package.json 文件, 或者 main 入口不存在或无法解析, 则 Node.js 将会试图加载目录下的 index.js 文件
  3. 如果以上两步都失败了, 则 Node.js 会在终端打印错误消息, 报告模块的缺失: Error:Cannot find module 'xxx'
javascript
// /code/mokuai/mokuai.js

console.log('通过 package.json 加载了 mokuai.js 文件')
javascript
// /code/mokuai/index.js

console.log('加载了 index.js 文件')
json
// /code/mokuai/package.json

{
  "main": "index.js"
}
javascript
// /code/test.js

require('./mokuai')

更新时间: