feng xiaohan
process

操作和控制当前进程的 api,并且挂载到 globalThis 下,是全局 API。

全局 API:不需要引入,直接调用。

ObjectDefineProperty(globalThis, "process", {
  __proto__: null,
  get() {
    return _process;
  },
  set(value) {
    _process = value;
  },
  enumerable: false,
  configurable: true,
});

api

platform

同 os。

arch

同 os。

argv

返回一个数组
0:运行工具地址
1:运行文件
2:运行命令后的参数

cwd

获取工作目录(同__dirname。)。

注意:在 esm 模式内不能使用__dirname,只能使用 cwd。

memoryUsage

返回内存信息

const res = {
  rss: 31006720, // 常驻集大小 物理内存的存量
  heapTotal: 6438912, // V8给我们分配的堆内存的总大小包括未使用的内存
  heapUsed: 5679584, //已经使用的内存
  external: 423221, //外部的内存++使用的
  arrayBuffers: 17606, //二进制的总最
};

exit

退出进程。可被监听。

process.exit();

process.on("exit", () => {});

kill

杀死进程。需要获取进程的 pid

setTimeout(() => {
  process.kill(process.pid);
}, 2000);

env

获取操作系统的所有环境变量。

在系统配置 -> 环境变量中可配。在当前进程中可修改,不影响操作系统全局

process.env.JAVA_HOME = "ssss";

应用

cross-env

用于设置环境变量的库。

原理:windows 调用SET 去设置环境变量;posix 使用 export 设置环境变量。

SSR 和 CSR

SSR

Nodejs 使用 DOM API

Nodejs 本身是没有 DOM 的,只有浏览器环境才有。

jsdom 是一个模拟浏览器环境的库,可以让我们在 Nodejs 中使用 DOM API。

const { JSDOM } = require("jsdom");
const fs = require("fs");
const root = new JSDOM(
  `<!DOCTYPE html><html><head></head><body><div id='app'></div></body></html>`
);
const window = root.window;
const document = window.document;

fs.writeFileSync("./index.html", root.serialize());

这个完全由服务端渲染的页面就属于 SSR(服务端渲染)

CSR

客户端渲染。(Vue、React 等 SPA)

SSR 和 CSR 区别

  • 页面加载方式:CSR 首屏渲染慢,SSR 快。
  • 内容生成和渲染:CSR 页面的生成和渲染由客户端 JS 负责,当数据发生变换时,JS 会重新生成并更新 DOM,从而实现内容动态变换;SSR 大部分都由服务端渲染好了,多为静态的。
  • 用户交互和体验:CSR 多用 ajax 或 websocket 与服务端进行数据交互,再通过 js 更新页面内容,这样可以拥有更快的页面切换和响应速度,但不利于搜索引擎爬虫和 SEO;SSR 对 SEO 优好

应用场景

  • SSR:密集型应用,ToC。新闻网站、博客网站、电子商务、门户网站等需要更高的 SEO 支持
  • CSR:ToB。后台管理系统,大屏可视化等
Nodejs 模块化

Nodejs 模块化遵循两套规范:CommonJS 和 ESM 规范。

CommonJS

使用require()引入模块,一共五种类型引入模块:自定义、第三方、内置、C++扩展、JSON 文件
使用module.exports导出,支持解构。

ESM

使用import引入,export导出。

注意: esm 不支持引入 JSON 文件。但是 node16 以上可以使用断言来引入:

import json from "./test.json" assert { type: "json" };

CommonJS 和 ESM 区别

  • CJS 是基于运行时同步加载,ESM 是基于编译时异步加载

    一般import语句只能在顶层,而require()可以在块级作用域底下;但是可以使用import()的函数模式,返回一个 promise

  • CJS 可以修改值,ESM 只读;
  • CJS 不支持 tree shaking,ESM 支持;
  • CJS 中顶层的 this 指向模块本身,而 ES6 中顶层 this 指向 undefined

原理

各个文件引入并使用的原理。

.json

使用 fs 直接读取文件内容并转换为 utf-8 字符串,然后再将其转换为对象。

.node

使用 process 读取文件内容。

.js

  1. 在类 weakmap 中查找是否有缓存,如果没有则通过 fs 读取文件内容并转换为 utf-8 字符串;
  2. 判断是否是以.js结尾,是则读取 package.json 的 type,如果不是 module 则获取父级以及确认报错的行号和错误信息;
  3. 如果都没问题则会调用_compile()进行编译,在这个方法中会将文件名和文件内容进行拼接,将文件内容包装进一个匿名函数中(类似 js 沙箱,隔离上下文环境);
  4. 调用 script 去执行这个匿名函数,并查看里面是否有动态引入的 import,有的话就引入对应模块,最后返回一个执行结果;
  5. 在拼接文件时,会传递五个参数,执行完匿名函数后会有去组装这五个参数(export、require、module、filename、dirname),再使用 ReflectApply 透传给匿名函数,最后返回结果。
npx

npx 是命令行工具,npm 5.2.0 以上新增的功能。让用户不安装全局包的情况运行已安装在本地项目中的包或远程包。

使用完安装包就删。

优势

  • 避免全局安装;
  • 总是使用你最新版本;
  • 可执行任意 npm package 包;

与 npm 区别

npx 更侧重于执行某个包(模块)的某个命令;
npm 侧重于安装和卸载这个包(模块);

运用场景

  • 运用脚手架工具执行安装模板命令
  • 可在命令行中执行没有在全局安装,但是在项目中安装好的包(模块)的命令;
    npx vite
    

    这样和在 script 编写好后再由 npm run 来执行是一样的。npx 能找到当前下载好的包并使用。

Nodejs

简介

  • Nodejs 是 JS 的运行时环境,依赖于 v8 引擎,v8 引擎由 c/c++编写,所以 js 代码会转化为 c/c++后在执行;
  • Nodejs 使用异步 I/O 和事件驱动设计理念,可以高效地处理大量并发请求,提供了非阻塞式 I/O 接口和事件循环机制;

    异步 I/O 最终都是由 libuv 事件循环库去实现的。

  • Nodejs 依靠 libuv,适合处理一些 IO 密集型应用

    由于 js 的单线程,很容易造成 cpu 占用率高,不适合 cpu 密集型应用(图像处理、音频处理等需要大量数据结构 + 算法);如果非要写可以使用 c++ 插件或 Nodejs 提供的 cluster。

应用

常见的一些库和框架都需要依赖 nodejs 环境。

其他

定义全局变量

Nodejs 可以使用global.xx去定义一个任何模块都能访问到的全局变量。

但是 ES2020 新增了一个globalThis可以兼容浏览器(window)和 Nodejs 的全局变量globalThis会自己根据环境变换为 window 或 global,可实现跨平台访问全局变量。

内置 API

  • __dirname:当前运行文件所在目录(绝对路径)
  • __filename:当前运行文件所在目录的执行文件(绝对路径)
  • Buffer
  • process
  • fetch

    node18+ 支持,和浏览器一样。

npm

Node Pacakge Manager。Nodejs 的包管理工具。

类似于:PHP-Composer,Java-Maven,Python-pip,Rust-Cargo。

package.json

{
  "name": "ferhannah-cli",
  "version": "1.0.1", // 主版本号(大版本架构更新).次版本号(功能更新).bug修复
  "description": "ferhannah-cli 脚手架工具",
  // 模块化相关--
  "main": "index.js", // 用户用的时候找的路径
  "type": "module",
  "module": "",
  "browser": "",
  //---
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c rollup.config.js",
    "build-ts": "rollup --config rollup.config.ts --configPlugin typescript"
  },
  "keywords": ["Vite", "Vue3", "React", "CLI", "Ant Design", "Element Plus"],
  "bin": {
    // 配置可执行文件
    "ferhannah": "./bin/index.js"
  },
  "devDependencies": {}, // 开发时依赖
  "dependencies": {}, // 生产环境依赖
  "peerDependencies": {}, // 对等依赖,一般是插件人员和npm包开发人员使用;如果需要安装开发的插件必须下载其依赖的插件(对等插件)
  // npm 仓库配置信息--
  "repository": {
    "type": "git",
    "url": ""
  },
  "files": [], // 发布后能看到的文件
  "homepage": "",
  "author": "fxh",
  "license": "ISC"
  //--
}

npm install 原理

npm install 发生了什么?

安装的依赖会存放在根目录的 node_modules 下,默认采用扁平化的方式安装。在遍历依赖书时使用广度优先算法逐层处理每个依赖包中的依赖,直到所有的依赖都被处理完毕。在处理依赖时,npm 会检查该依赖的版本号是否符合依赖树中其他依赖版本要求,如果不合适则会尝试安装合适的版本。

如果发现需要相同依赖的不同版本,则会在需要该依赖的包下又新建一个 node_modules,再将依赖装进去。

  • 先检查 npm config;
  • 在当前目录下寻找.npmrc文件(项目级别),如果找到;
  • 再找当前用户的.npmrc文件(用户级别)
  • 再找全局的.npmrc文件(全局:AppData)
  • npm 内置的.npmrc文件(nodejs)
  • 检查是否有 package-lock.json 文件。
    • 有:则比较package.jsonpackage-lock.json内的依赖版本是否一致。
      • 不一致:会根据package.json下载并更新package-lock.json

        针对 npm 高版本,现在基本上都是。

      • 一致:检查缓存(之前是否安装过);
        • 有:直接解压到 node_modules。
        • 没有:去 npm 官网/镜像下载资源包,检查完整性,添加到缓存,更新package-lock.json文件;
    • 没有:获取包信息,构建依赖树和扁平化依赖,然后检查缓存;
      • 有:直接解压到 node_modules。
      • 没有:去 npm 官网/镜像下载资源包,检查完整性,添加到缓存,更新package-lock.json文件;

package-lock.json

可以锁定版本,记录依赖树的详细信息

  • version:包版本号;
  • resolved:包的下载地址;
  • integrity:验证包的完整性的哈希值(摘要);
  • dev:当前包是一个开发依赖包;
  • bin:包中可执行文件的路径和名称;
  • engines:包所依赖的 Nodejs 版本范围;

它还帮我们做了缓存通过 name + version + integrity 生成一个唯一的 key,这个 key 用于映射 npm 缓存文件(一般在 npm-catch 下),如果生成的 key 能与 integrity 对上,就会从映射文件中取出缓存的二进制包并解压出来使用

npm run 原理

npm run 发生了什么?(也就是指定 node 命令时)

nodejs 的所有命令都在 node_modules/.bin 下的可执行文件中。nodejs 支持跨平台、跨系统:

  • .sh:Unix、Linux 以及 MacOS 使用;
  • .cmd:cmd 执行文件(windows);
  • .ps1:powershell 执行文件(windows);

命令是在 package.json 中的 bin 内配置的。

  • 会在当前项目找 node_modules/.bin 中是否有该命令环境;
  • 再去全局的 node_modules 下找;
  • 再去环境变量中找;
  • 都找不到报错;

npm 生命周期

在执行 dev 之前和之后都有对应的生命周期使用,执行 dev 会自动调用 predev 和 postdev:

{
  "scripts": {
    "predev": "node prev.js",
    "dev": "node index.js",
    "postdev": "node post.js"
  }
}

应用场景:

  • predev:打包之前清除 dist 目录;
  • postdev:CI 脚本,发布之后自动将代码提交;

注意:dev 命令可以换成其他任何自定义命令,周期命令的配置也一样。

npm 私服

优势:

  • 可以离线使用;
  • 提高包的安全性,避免公共 npm 包出现漏洞;
  • 提高包的下载速度;

搭建

https://verdaccio.org/zh-cn/docs/installation/

npm install -g verdaccio

npm ci

npm install,但是是一种更干净的安装:

  • 要求 package-lock.json 等锁依赖版本的文件必须存在,且版本必须和 package.json 中要求的版本对应,否则会报错;
  • 在安装之前先把 node_modules 文件夹(下文可能会称为 modules)删除;
  • 无法单个安装;

场景:一般用于 docker 镜像构建时使用。

modules和packages

modules

在 node 中模块(modules)采用的是 CommonJS 规范,该规范定义了:

  • 一个文件就是一个模块;
  • 每个模块都有单独的作用域;
  • 通过module.exports导出成员;
  • 我们可以通过require函数载入任何文件目录下的模块;

注:更多详情见模块化编程一篇。

image-20230828163817383

packages

一个 packages 是一个文件夹,该文件夹的根目录下必须包含一个package.json,有了这个 json 文件就可以将其发送到 npm 上。packages 一般是由多个模块组成的一个模块包。

如果需要通过reqire()加载到这个模块,它必须有:

  • 一个定义模块功能的 js 文件;
  • package.json里拥有mian字段来指向入口文件

注意:引用的 packages 应该在 node_modules 目录底下,这样才能被 node 识别到!!!

image-20230828173317292

npm

背景

过去常常使用script标签将外部资源库直接放在 HTML 中,这会暴露一个全局变量供我们使用,但是当项目很大的时候,这些变量很难被管理:

  • 在 HTML 中加载所有的 JS 文件,这会让其变得混乱;
  • 很多时候我们实际上会在计算机中直接下载一个库文件(例如 JQuery),但是每当新版本出来时,我们不得不手动下载并更新这些文件;
  • 在 npm 之前没有包含我们所需要的所有包的一个个存储库,这对于我们手动下载库并管理它们增加了很大的困难;

什么是 npm

Node Package Manager。它既是我们计算机上的一个软件,也是一个 Node.js 包存储(管理)库,用于 Node 插件的管理(安装、卸载和管理依赖等)。

下载

npm 会随着 Node.js 安装而安装。

常用指令

本章会持续更新。

检查 npm 版本

npm -v

初始化一个包管理文件(package.json)

这个文件存储了整个项目的配置。

npm init

它会让你填写一些基本信息。

安装包

npm install 包名
npm i 包名
npm i 包名1 包名2
npm i 包名@版本号

注意:如果不指定版本号,默认下载的是最新的版本。

安装 package.json 中所有的依赖包:

npm i

全局安装包:

这样在计算机上每一个项目都可以使用:

npm i 包名 -g

设置安装包的依赖:

  • 生产环境依赖

    该选项会将包下载到该项目(地址)下,这些包是我们项目中所需要的依赖(比如 UI 组件库,JS 部分工具包,框架等):

    npm install 包名 --save
    
  • 开发环境依赖

    该选项会将包下载到该项目(地址)下,一般是开发过程中需要借助它完成一些工作,但是项目内容并不需要它(比如打包工具 webpack 等):

    npm install 包名 --save-dev
    

卸载包

npm uninstall 包名

查看当前 npm 镜像

npm config get registry

切换镜像

npm config set registry https://registry.npmjs.org

上面是 npm 原镜像,还有:

  • 淘宝镜像:

    npm config set registry https://registry.npm.taobao.org
    

    23.8.21 淘宝镜像修改了:

    https://registry.npmmirror.com
    
  • hh 镜像:

    npm config set registry http://nexus.lan:8081/repository/npm-public/
    

查看代理

npm config get proxy

更多权限

可以在 npm 语句前加上一个 sudo 来获取更高的权限,它会让你输入用户和密码(Linux):

sudo npm i

打包

将开发的代码进行打包,打包后的代码可以发布到 npm 公共仓库里供其他人使用,打包后的文件是以.tgz为后缀。

npm pack

默认打包后的文件名是以 package.json 里的 name 和 version(版本号)用-拼接而成。

指定打包名称

npm pack . --name=mylibname

登录 npm 账号

npm login

登出 npm 账号

npm logout

发布包到 npm 上

注意:执行该操作必须要先登录 npm 的账号!

npm publish

package.json 配置信息

package.json 是 npm 管理包的文件,使用npm init后自动创建的,里面配置了包的所有信息(名字、版本号、依赖项等)。下面是它具体配置信息:

{
  "name": "element-plus", // 包名。如果要发布这个包,name是必填的,它是发布包的名字;如果不发布就不是必须的 (name)
  "version": "1.0.0", // 版本号。如果要发布这个包,version是必填的(version)
  "description": "A Component Library for Vue 3", // 包的描述(description)
  "keywords": [
    // 包的关键字(keywords)
    "element-plus",
    "element",
    "component library",
    "ui framework",
    "ui",
    "vue"
  ],
  "homepage": "https://element-plus.org/", // 该包项目的首页地址(可以是文档链接地址、项目官网等)
  "bugs": {
    // 项目提交问题地址(bugs)
    "url": "https://github.com/element-plus/element-plus/issues"
  },
  "license": "MIT", // 表示项目的开源许可证(license)
  "author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)", // 跟包相关的人的信息 (people fields: author, contributors)
  "funding": {
    // 指定捐赠地址(如果对你有帮助的话可以请我喝咖啡。。)(funding)
    "type": "individual",
    "url": "http://example.com/donate"
  },
  "files": [
    // 包作为依赖项(dependency)时(就是作为我们所下载的包)所需包含的文件(files)
    "LICENSE",
    "README.md",
    "index.js",
    "cjs/",
    "umd/",
    "jsx-runtime.js",
    "jsx-dev-runtime.js",
    "react.shared-subset.js"
  ],
  "main": "lib/index.js", // 包的入口文件(main)
  "browser": {
    // 浏览器环境下指定入口文件(默认是在node的环境下)
    "my-module": "./src/browser.js"
  },
  "bin": "./path/to/program", // (bin)
  "man": "./man/doc.1", // 指定单个文件或文件名数组,以供man查找。
  "directories": {
    // 指定项目的目录结构(可选,默认为CommonJS的结构)
    "lib": "src/lib", // 指定项目的库文件目录
    "bin": "src/bin", // 指定项目的可执行文件目录
    "man": "src/man", // 指定项目的手册文件目录
    "doc": "src/doc" // 指定项目的文档文件目录
  },
  "repository": {
    // 代码所在的位置,通常在github上
    "type": "git",
    "url": "git+https://github.com/element-plus/element-plus.git"
  },
  "scripts": {
    // 定义在包不同的生命周期下的脚本命令(scripts)
    "start": "node app.js",
    "start-server": "node app.js"
  },
  "config": {
    // 用于设置script字段里的脚本运行时的参数(config)
    "port": "8080"
  },
  "dependencies": {
    // 生产环境依赖包(dependencies)
    "lodash": "^4.17.21"
  },
  "devDependencies": {}, // 开发环境依赖包 (devDependencies)
  "peerDependencies": {
    // 指定包的对等依赖(peerDependencies)
    "soy-milk": "1.2"
  },
  "peerDependenciesMeta": {
    // 对对等依赖的配置
    "soy-milk": {
      "optional": true // 对等依赖可选(该项目下没有安装该包不会发出警告)
    }
  },
  "bundleDependencies": [
    // 指定指定哪些依赖项应该被捆绑到包中(bundleDependencies)
    "renderized",
    "super-streams"
  ],
  "optionalDependencies": {
    // 指定可选依赖项(optionalDependencies)
    "@element-plus/icons-vue": "^2.0.6"
  },
  "overrides": {
    // 覆盖版本依赖
    "foo": "1.0.0"
  },
  "engines": {
    // 指定包所依赖的运行时环境或引擎(一般为node和npm)
    "node": ">=0.10.3 <15",
    "npm": ">=6.0.0"
  },
  "os": ["linux", "!win32"], // 指定包所依赖的操作系统
  "cpu": ["!arm", "x64"], // 指定包所依赖的cpu(可以使用!来表示非)
  "private": false, // 设置包的私有性,如果将其设置为true,npm会拒接这个包的publish
  "publishConfig": {
    // 设置包发布的配置属性,只有在进行npm publish时才会生效,不会影响其他npm命令的行为
    "registry": "https://registry.npmjs.org/", // 指定发布到的npm仓库的URL(默认为npm官方仓库)
    "access": "public", // 指定发布的包的访问级别(默认为public,如果设置为"restricted",则只有特定的用户或团队才能访问该包)
    "tag": "latest" // 指定发布的包的标签。可以设置为"latest"(最新版本,默认值)或其他自定义标签
  },
  "workspaces": [
    // 定义工作区(workspaces)
    "./packages/*"
  ]
}
  • name:name 字段遵循以下规则:

    • 不多于 214 个字符;
    • 作用域包的名称可以以点或下划线开头。没有作用域是不允许这样做的;
    • 不可以使用大写字母,不要使用”js“或”node“来命名;
    • 不能包含任何非 url 安全的字符,因为该名称最终成为 URL、命令行参数和文件夹名称的一部分;
  • version:当包发生变化时(再次发布包时),版本号也要随之发生变化;且它必须遵循node-semver的版本命名规范(x.y.z)。

    semver:Semantic Versioning,语义化版本号。

    • X 为主版本号(major): 通常在涉及重大功能更新,产生了破坏性变更时会更新此版本号;
    • Y 为次版本号(Minor):在引入了新功能,但并未产生破坏性变更,依然向下兼容时更新此版本号;
    • Z 为修订号(Patch):在修复了一些问题,但未产生破坏性变更时会更新此版本号;

    name 和 version 是用于确定一个唯一包的标识。

  • description :字符串类型,在搜索的时候可以看到该包的描述,并且该描述也可以作为搜索的关键词。

  • keywords:字符串数组类型,作为搜索该包的关键词。

  • bugs:可以是一个字符串,也可以是一个对象类型。该地址一般是 github 上的 issues 地址或者邮箱地址。

    {
      "url": "https://github.com/owner/project/issues",
      "email": "project@hostname.com"
    }
    
  • license:开源许可证,让其他开发者知道如何允许使用这个包,以及操纵这个包的具体限制。如果不想开源,可以配置:

    {
      "license": "UNLICENSED",
      "private": true
    }
    
  • people fields:包的作者或贡献者(维护者)的信息,可以是一个字符串,也可以是一个由 people 组成的数组,该数组每项格式可以有以下两种:

    {
      "name": "Barney Rubble",
      "email": "b@rubble.com",
      "url": "http://barnyrubble.tumblr.com/"
    }
    
    {
      "author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)"
    }
    
  • funding:可以是对象,字符串,数组(数组项为对象和字符串都可以)。

  • files:匹配文件的字符串数组,可以用来指定哪些文件需要发布到 npm 上以此来控制 npm 包的大小,如果指定的是文件夹,则对应文件夹下的所有文件都会被发布。匹配文件的字符串规则和.gitignore是一样的,也可以在项目的根目录下创建一个.npmignore文件,.npmignore的优先级高于.gitignore

    注意:如果不配置该选项,则默认值为[*],也就是会包含所有的文件。

  • main:其他开发者引用该包是最先进入的文件。它是一个相对于包文件夹根目录的模块,并且一般来说只会存在一个。

    注意:如果不配置该选项,则默认为index.js为包的入口根文件。

  • bin:让包可以作为命令行工具使用。上述配置如下:

    "bin": {
      "element-plus": "./path/to/program"
    }
    
  • scripts:在 package.json 中有很多内置的生命周期事件(key),或者我们自定义的命令,在这些事件下我们可以执行任意脚本。

    "scripts": {
      "prestart": "npm install", // 在脚本启动前自动执行这个命令 @1
      "start": "node app.js", // 启动脚本时执行
      "install" : "scripts/install.js", // 下载时执行
      "postinstall": "npm run build", // 安装依赖项后自动执行
      "build": "webpack",
      "uninstall" : "scripts/uninstall.js", // 卸载时执行
      "start-server": "node app.js", // @2
      "dev": "nodemon index.js" // @2
    }
    

    @1:使用start脚本名称简化运行命令,让他人也能轻松运行入口代码文件:

    npm start
    

    @2:对于自定义的执行命令脚本名称,需要使用run来执行:

    npm run start-server
    npm run dev
    

    更多生命周期讲解请见官网。

  • config:用于配置scripts字段里脚本运行的参数,例如配置端口号(port):

    "config": {
      "port": "8080"
    }
    

    执行脚本时,这个参数会变成npm_package_config_port存储到我们的环境变量中,我们可以通过:

    console.log(process.env.npm_package_config_port); // 8080
    

    访问到我们设置的端口号(port)。

  • dependencies:运行所需要用的依赖,在该目录下的依赖会在生产环境中。一般是使用npm install <packagename>npm install <packagename> --save下载以来后自动插入里面的依赖,但是也可以手动在其中添加依赖,然后再通过npm install下载这些依赖。其中依赖的版本范围可以是如下几种:

    • 版本字符串

      版本字符串必须遵守 semver。

      "dependencies": {
        "lodash": "^4.17.21",
        "@types/lodash": "^4.14.182", // @3
        "elf": "~1.2.3",
        "foo": "1.0.0 - 2.9999.9999",
      },
      

      @3:npm 包都有一个名字,如果该名字被占用了别人就不能使用了,但是如果后续需要提供与该包名有关的包则可以使用@。npm 官方提供了一个作用域的概念,使用@来表示一个作用域,该作用域遵循包名的通常规则(即 url 安全字符),这样就可以在我们自己的作用域中添加包名了。(一般是用来补 ts 声明哈哈)

    • tarball URLs

      使用 tarball URL 指向的一个压缩文件包地址,tarball URL 的格式通常是一个以.tar.gz.tar.bz2结尾的链接,指向一个可访问的.tar文件。

      "dependencies": {
        "asd": "http://asdf.com/asdf.tar.gz",
      },
      
    • Git URLs

      使用 git urls 直接从 git 仓库上拉取包。格式如下:

      <protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
      

      例如:

      "dependencies": {
        "asd1": "git+ssh://git@github.com:npm/cli.git#v1.0.27",
        "asd2": "git+ssh://git@github.com:npm/cli#semver:^5.0",
        "asd3": "git+https://isaacs@github.com/npm/cli.git",
        "asd4": "git://github.com/npm/cli.git#v1.0.27",
      },
      
    • GitHub URLs

      可以直接指向 github 项目目录地址。

      "dependencies": {
        "express": "expressjs/express",
      },
      
    • Local Paths

      支持本地依赖地址。(一般用于测试本地依赖,要发布到 npm 上的包不应该使用该方法)

      "dependencies": {
        "local-module": "file:../local-module-plugin/local-module-0.0.0.tgz"
      },
      

      当前根目录的上级目录下的local-module-plugin文件夹下的local-module-0.0.0.tgz包,该包由依赖项目npm pack生成而来。

  • devDependencies:开发阶段时需要的依赖包,例如一些打包工具,类型检查工具等(webpack、eslint),在生产环境不会出现这个依赖包。一般使用npm install <packagename> --save-dev或者npm install <packagename> -D安装。

  • peerDependencies:确保包与特定版本的对等包兼容,并避免在运行时出现不兼容或错误的情况。

    当当前包依赖于其他包时,通常会在 dependencies 字段中声明这些依赖关系。但是,有时该包可能需要与特定版本的其他包进行交互,而不是简单地依赖于它们。这时,可以使用 peerDependencies 字段来指定这些对等依赖。

    "peerDependencies": {
      "package-name": "version-range"
    }
    

    package-name是该包所依赖的对等包的名称,version-range是对等包的版本范围。

    包被安装时,npm 会检查对等依赖关系,并确保对等包的正确版本已经安装。如果对等包的版本不符合要求,npm 会发出警告提示。

    注意:peerDependencies 字段只是声明对等依赖关系,并不会自动安装对等软件包。用户在使用这个包时,需要手动安装对等包。因此,在文档或说明中明确指出对等依赖关系,并提供安装指南是很重要的。

  • bundleDependencies:发布一个软件包时,通常会将其上传到包管理器(npm)供其他人使用。默认情况下,npm 会自动下载和安装软件包的所有依赖项(package.json)。然而,有时我们项将某些依赖项捆绑到包中,以便用户在安装您的软件包时不需要下载这些依赖项。

    注意:这会增加包的大小。

  • optionalDependencies:提供更大的灵活性和兼容性,允许包在某些依赖项无法满足或无法安装的情况下仍然正常运行。这对于与其他包进行集成或依赖于特定平台功能的软件包特别有用。

    注意:optionalDependencies 字段只是声明可选依赖关系,并不会自动安装对等软件包。用户在使用这个包时,需要手动安装对等包。因此,在文档或说明中明确指出可选依赖关系,并提供安装指南是很重要的。

  • workspaces:允许我们在一个 package.json 中的工作区中管理多个相关的子包。它是一个文件匹配字符串的数组。例如:在./packages文件夹下的所有文件都将视作和当前 package.json 在一个工作空间下。

    "workspaces": [
        "./packages/*"
    ]
    

    工作区功能可以帮助简化多包项目的管理和开发过程,提高效率。它适用于需要同时管理多个相关包的项目,例如 monorepo 或微服务架构。

在 Node(Express)中上传或下载文件

在前端页面上传文件表单时,设置一个文件上传器(将输入文本类型设置为file):

<input type="file" name="image" id="image" />

Multer

纯文本可以使用body-parser

const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false })); // 使用urlencoded解析器解析传入请求体,要求传入的数据为文本数据

由于文件不是纯文本,而是二进制数据,我们需要下载一个文件请求解析中间件——multer来识别上传的文件:

npm install --save multer

multer能解析文件请求数据,能够处理文件请求或文本和文件混合的请求数据。

发送文件数据

  • 需要在客户端发送表单数据的时候添加enctype来选择发送解析的数据类型

    <form action="/profile" method="post" enctype="multipart/form-data">
      <input type="file" name="image" id="image" />
    </form>
    

    multipart/form-data:请求不包含纯文本,而是包含文本和二进制的混合数据。

获取发送的文件数据

  • 引入multer,将其注册为中间件

    const express = require("express");
    const multer = require("multer");
    
    const app = express();
    
    app.use(multer().single("image")); // 获取一个文件,文件名为name中的名字 @1
    

    @1:multer 必须作为一个执行函数注册,它可以调用其他的方法来设置获取文件的形式(如单个文件或多个文件)。

  • 获取上传的文件数据

    app.post("/add-product", postAddProduct);
    
    exports.postAddProduct = (req, res, next) => {
      const imageUrl = req.file; // req.file中获取文件数据信息 @2
    };
    

    @2:它会返回一个 buffer 的混合数据流,我们可以设置它为一个图片结构(图片名为没有文件扩展名的随机的哈希名称,添加扩展名可以恢复图片):

    app.use(multer({ dest: "images" }).single("image"));
    

存储文件数据

  • 设置存储引擎:

    diskStorage()是 multer 的存储引擎,它可以通过传递一个 JS 对象来配置,他需要两个 key:

    • destination:存储位置(存储文件路径);
    • filename:存储名字
    const fileStorage = multer.diskStorage({
      destination: (req, file, cb) => {
        cb(null, "images");
      },
      filename: (req, file, cb) => {
        cb(null, file.filename + "-" + file.originalname);
      },
    });
    
    app.use(multer({ storage: fileStorage }).single("image"));
    

    这样文件就会以指定的名字存储到指定的路径文件中。

使用Node(Express)发送邮件

Node(Express)能创建一个服务端(器),但是创建一个邮件服务器是很困难的,处理邮件与处理传入的请求和响应完全不同。我们基本上没有办法创建自己的邮件服务器,所以一般使用第三方的邮件服务器

第三方邮件服务器:MailChimp、SendGrid 等

下载 Nodemailer

需要利用 nodemailer 发送邮件:

npm install --save nodemailer

使用 SendGrid 作为第三方邮件服务器

使用 SendGrid 作为第三方邮件服务器需要安装nodemailer-sendgrid-transport来与之交互:

npm install --save nodemailer-sendgrid-transport

注意:注册 SendGrid 账号和开启后续服务均需要挂 VPN,连接外网。

初始化传输器

app.js

const nodemailer = require("nodemailer"); // 引入nodemailer
const sendgridTransport = require("nodemailer-sendgrid-transport"); // 引入nodemailer-sendgrid-transport

const transporter = nodemailer.createTransport(
  sendgridTransport({
    auth: {
      api_user: "fxh", // SendGrid用户名
      api_key: "dsafdsfdsf", // SendGrid获取的密钥
    },
  })
); // 设置node传输邮件的方式,并通过sendgridTransport执行(这样可以用nodemailer配置SendGrid)

发送邮件

通过sendMail()发送邮件,它返回一个 Promise:

transporter.sendMail({
  to: userEmail, // 邮件发送的地址(用户的邮件地址)
  from: "shop@node-cm.com", // 发出邮件的地址
  subject: "Signup succeeded", // 邮件主题
  html: "<h1>You successfully signed up!</h1>", // 邮件内容
});