feng xiaohan
Rollup + TS

Rollup 是一种 JavaScript 模块打包器,用于将多个模块打包成一个单独的文件。使用 Rollup 打包(构建)ts 项目:

  • 更小的包体积:Rollup 可以将代码压缩到最小,因为它只包含实际使用的代码,而不是整个库或框架。

    相比 webpack,Rollup 打包后的体积更小。

  • 更快的加载速度:由于 Rollup 生成的代码更小,因此它加载更快,这对于移动设备和低带宽连接非常有用。

    Rollup 适合开发一些小的框架、控件等。

  • 更好的 Tree-shaking:Rollup 可以识别和删除未使用的代码,因此可以更有效地进行 Tree-shaking。

    Tree-shaking 是一种 JavaScript 代码优化技术,它可以自动删除未使用的代码,以减小代码的体积,提高应用程序的性能。

    它需要使用支持 ES6 模块语法的打包工具,例如 Webpack、Rollup 等。在使用这些工具时,需要确保在配置中启用 Tree-shaking 功能,以便在打包时自动删除未使用的代码。

  • 支持 ES6 模块:Rollup 原生支持 ES6 模块,因此可以使用 ES6 的 import 和 export 语法,而不需要使用特殊的语法或工具。

  • 插件生态丰富:Rollup 有许多插件可用于优化、压缩和转换代码,以及处理其他类型的文件,例如 CSS 和图像。

安装

全局安装 rollup

npm install rollup-g

需安装以下的包:

package.json

{
  "name": "rollupTs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=development  rollup -c -w",
    "build": "cross-env NODE_ENV=produaction  rollup -c"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cross-env": "^7.0.3", // 在命令时区分生产和开发环境
    "rollup-plugin-livereload": "^2.0.5", // 安装热更新
    "rollup-plugin-node-resolve": "^5.2.0", // 引入外部依赖
    "rollup-plugin-replace": "^2.2.0", // 替换环境变量给浏览器
    "rollup-plugin-serve": "^1.1.0", // 安装rollup web服务
    "rollup-plugin-terser": "^7.0.2", // 安装代码压缩插件
    "rollup-plugin-typescript2": "^0.31.1", // 安装TypeScript转换器,它会去读取tsconfig.json文件
    "typescript": "^4.5.5"
  }
}

配置

需要在 rollup.config.js 里配置 Rollup 相关信息:

// rollup.config.js
console.log(process.env); // cross-en环境变量信息
import ts from "rollup-plugin-typescript2"; // 识别ts文件
import path from "path";
import serve from "rollup-plugin-serve"; // 启动前端服务
import livereload from "rollup-plugin-livereload"; // 热更新服务
import { terser } from "rollup-plugin-terser"; // 代码压缩
import resolve from "rollup-plugin-node-resolve";
import repacle from "rollup-plugin-replace"; // 在打包过程中替换代码中的变量或字符串 @5

const isDev = () => {
  // 设置开发环境 @7
  return process.env.NODE_ENV === "development";
};
export default {
  input: "./src/main.ts", // 入口文件 @1
  output: {
    file: path.resolve(__dirname, "./lib/index.js"), // 出口(输出)文件地址,也就是打完包后的文件
    format: "umd", // 指定输出文件的格式(模块化规范)
    sourcemap: true, // 开启源map,可以让打包后的代码对应到源代码,方便调试 @3
  },

  plugins: [
    // 配置插件
    ts(), // 识别ts文件,它会自动读取tsconfig.json
    terser({
      // 代码压缩 @2
      compress: {
        drop_console: !isDev(), // 配置打包后自动删除console.log()
      },
    }),
    repacle({
      // @6
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    }),
    resolve([".js", ".ts"]),
    isDev() && livereload(), // 在开发环境下,livereload()启动热更新服务 @4
    isDev() &&
      serve({
        // 在开发环境下,serve启动前端(页面)服务
        open: true, // 是否打开页面
        port: 8089, // 页面打开的端口
        openPage: "/public/index.html", // 页面文件
      }),
  ],
};

@1:rollup 不认识 ts,所以需要安装识别 ts 的插件rollup-plugin-typescript2

@2:会将代码压缩打包。但是代码压缩后不方便调试查看。

@3:开启源 map,可以让打包后的代码对应到源代码,方便调试。需要在 tsconfig.json 里面同样配置:

"sourceMap": true

@4:热更新服务会在启动前端(页面)时,修改文件页面会自动刷新;

@5、@6:由于浏览器里不支持 process.env,它是在 node 端支持的,所以需要将其从 node 里面注册到浏览器中,可以使用rollup-plugin-replace来进行替换;

@7:如果是开发环境则会启动包含isDev()的配置(如热更新,启动前端打开页面等),如果是生产环境则不启动;

打包基本流程:

  • 首先需要一个打包的入口文件,一般为 index.js;
  • 然后再 package.json 里的 main 指定这个文件;

启动命令

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "rollup -c -w", // 读取rollup.config.json文件,并监听,如果该文件有改动则会自动重启rollup
    "build":"rollup -c" // 读取rollup.config.json文件
},

rollup -c之后会产生一个在指定路径打包好的文件。

esbuild + SWC + TS

esbuild

web 构建工具,速度相对于其他构建工具(parcel2、webpack5、rollup + terser)来说要快很多。

它是基于 Go 去开发的,采用了 Go 语言的多线程执行,所以性能要比 JS 要好。

主要特性:

  • 极快的速度,无需缓存即可实现基础打包;

  • 支持 ES6 和 CommonJS 模块;

  • 支持对 ES6 模块进行 tree shaking

    Tree-shaking 是一种 JavaScript 代码优化技术,它可以自动删除未使用的代码,以减小代码的体积,提高应用程序的性能。

    它需要使用支持 ES6 模块语法的打包工具,例如 Webpack、Rollup 等。在使用这些工具时,需要确保在配置中启用 Tree-shaking 功能,以便在打包时自动删除未使用的代码。

  • API 可同时用于 JavaScript 和 Go;

  • 兼容 TypeScript 和 JSX 语法;

  • 支持 Minification;

    生成的代码会被压缩而不是格式化输出。压缩后的代码与未压缩代码是相等的,但是会更小。这意味着下载更快但是更难调试。 一般情况下在生产环境而不是开发环境压缩代码。

  • 支持 plugins(强大的插件系统)

SWC

处理打包之后对不同浏览器的兼容问题。

使用 Rust 语言编写,用于代替 Babel(Babel 是用于处理不同浏览器之间的兼容问题),比 Babel 快很多。

安装

npm install @swc/core esbuild @swc/helpers
  • @swc/core: swc 的核心包,用于编译 JavaScript 和 TypeScript 代码;
  • esbuild:一个快速的 JavaScript 和 TypeScript 构建工具;
  • @swc/helpers: swc 的辅助包,用于转换 JSX 代码(没有 JSX 可不选);

配置

需要修改 tsconfig.json,以支持更高级的语法:

"compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node"
}

在 config.ts 里编写打包的配置:

// config.ts
import esbuild from "esbuild"; // 引入打包工具
import swc from "@swc/core"; // 引入SWC库(类似于babel es6 转 es5)
import fs from "node:fs";

await esbuild.build({
  // @1
  entryPoints: ["./index.ts"], // 入口文件(可支持多个)
  bundle: true, //模块单独打包
  loader: {
    // 配置xx-loader
    ".js": "js",
    ".ts": "ts",
    ".jsx": "jsx",
    ".tsx": "tsx",
  },
  treeShaking: true, // 代码优化
  define: {
    "process.env.NODE_ENV": '"production"',
  },
  plugins: [
    {
      //实现自定义loader
      name: "swc-loader",
      setup(build) {
        build.onLoad({ filter: /\.(js|ts|tsx|jsx)$/ }, (args) => {
          // args:当前编译文件的信息
          // console.log(args);
          const content = fs.readFileSync(args.path, "utf-8");
          const { code } = swc.transformSync(content, {
            // 转成es5的代码
            filename: args.path,
          });
          return {
            contents: code,
          };
        });
      },
    },
  ],
  outdir: "dist", // 出口
});

@1:esbuild 打包是异步的,在 ES2017 以后可以使用顶层 await 语法。

注意:使用ts-node-esm config.ts时,如果没有-esm需要升级一下这个方法,并且将 package.json 里的"type": "module"

组件逻辑复用

单文件组件

.vue 结尾的文件,在 <script setup> 中定义组件逻辑,在 <template> 中定义组件模板。可复用组件及其对应逻辑。用于构建模块

组合式函数

约定俗成以 use 开头的函数,他可以将公共任务逻辑(有状态的逻辑)抽离出来,以函数的形式复用;在 use 函数内部同样可以使用 Vue 的钩子函数和语法,执行顺序在使用组件前。

官网的例子是追踪鼠标变换。在实际架构中,会创建一个 hook 文件夹,根据 use 函数的功能,可分为 core、event、setting、web 等文件夹,分别存放对应的 hook 函数

  • core:存放核心通用逻辑,比如 useAtrrs hook 用于组合属性或类名前缀等。
  • setting:存放相关配置,比如一些全局的属性(后台应用的菜单详情配置),颜色十六进制转换,不同状态映射关系,还可以将 store 存储的信息用 computed 进行 ref 封装。
  • event:存放事件相关的逻辑,比如 useScroll 滚动事件处理,在里面可以配置防抖节流操作。
  • web:存放与 web 相关的逻辑,比如 useWebSocket 封装 websocket,useDesign 设置不同类名前缀,use18n 配置国际化等。
  • component:…

组合式函数传参

组合式函数的执行顺序在当前组件之前,对于需要在当前组件内动态传参赋值时,可以在组合式函数内部使用 toValue()watchEffect()来转换和监听

也就是接收一个 ref 或 getter 函数,而 toValue() 可以将 ref 或 getter 规范化为值

最佳实践

  • 命名:use 开头的驼峰命名法
  • 动态输入参数:watch + ref/geeter 或 **watchEffect + toValue()**;
  • 返回值:非响应式对象,该对象中包含多个 ref 值
  • 副作用:确保在组件卸载的生命周期中清理副作用,也可以封装专门的组合式函数来处理注册和清除;

    在组合式函数中添加 DOM 实践监听器或请求数据等操作。

  • 使用限制:只能<script setup> 或 setup() 钩子中被同步调用。某些情况下也可以像onMounted这样的生命周期钩子中调用。

    这些限制是为了 Vue 能确定当前活跃的组件实例的上下文,用于注册生命周期钩子,添加计算属性和监听器,以便于组件卸载时停止监听,避免内存泄漏。

  • 适当抽取组合是函数改善代码结构:组合式 API 可以更灵活的将逻辑部分抽离。
  • 选项式 API:组合式函数必须在 setup() 中调用,且必须在 setup() 中返回以便暴露给模板;

与 Mixin 的对比

Mixin 主要是针对 Vue2,相比组合式函数,它主要的问题有:

  • 不清晰的数据来源;
  • 命名空间冲突;
  • 隐式的跨 mixin 交流;

与无渲染组件的对比

一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。对比它来说组合式函数的优势是:

  • 不会产生额外的组件开销

所以在纯逻辑复用时还是该使用组合式函数,

自定义指令

注册自定义指令,主要是用于重用涉及普通元素的底层 DOM 访问逻辑

<script setup>,任何以* v 开头的驼峰式命名的变量都可以当作自定义指令都可以在模板中使用,并且不需要导入。

自定义指令对象中提供几种生命周期钩子函数,钩子函数的默认参数有:

  • el:DOM 元素;
  • binding:对象,包含以下属性:
    • value:传递给指令的值(value);
    • oldValue:之前的值,仅在 beforeUpdateupdated 钩子中可用;
    • arg:传递给指令的参数(:后的参数);
    • modifiers:修饰符对象(.后的参数);
    • instance:组件实例(谁用是谁的);
    • dir:指令的定义对象;
  • vnode:底层 vnode;
  • prevNode:之前的渲染中指令绑定的 vnode,仅在 beforeUpdateupdated 钩子中可用;

当需要操纵 DOM 时才使用,一般将自定义指令放在 directives 文件夹下,在挂载 app 前就将所有的自定义指令注册到了全局(setupGlobalDirectives)

使用方式

  1. <script setup> 中:
<script setup>
const vHighlight = {
  mounted: (el) => {
    el.classList.add("is-highlight");
  },
};
</script>

<template>
  <p v-highlight>This sentence is important!</p>
</template>
  1. setup() 函数中:
export default {
  setup() {},
  directives: {
    // 在模板中启用 v-highlight
    highlight: {
      /* ... */
    },
  },
};
  1. 全局注册:
const app = createApp({});
app.directive("highlight", {
  /* ... */
});

其他

  • 动态参数:自定义指令的参数可以是动态的,他会基于这个动态参数响应式地更新;
  • 简化形式:可进行简化操作,在不指定钩子函数时,默认的回调函数会在 mountedupdated都执行
  • 对象字面量:可向自定义指令传递多个值,以对象字面量形式传递,在 bingding.value 中接收;
  • 组件上使用(*):会始终应用于组件的根元素(和透传 attributes 类似),对于多根组件会忽略该指令;

    不推荐在组件上使用。

插件

为 Vue 添加全局功能的工具代码。一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身,使用 app.use() 安装。

应用:

  • 全局注册组件、指令;

    组件库。

  • 通过 app.provide() 向应用注入整个资源;
  • 通过 app.config.globalProperties 添加全局实例属性或方法;
fs

Nodejs 核心 API。提供与文件系统交互功能,包括读写、修改文件权限,创建目录等操作。

读取文件 - readFile

readFile - 异步

可指定文件编码格式,不会阻塞下面代码。

fs.readFile(
  "./util.md",
  {
    encoding: "utf-8",
    flag: "r",
  },
  (err, data) => {
    if (err) {
      return;
    }
    console.log(data);
  }
);

readFileSync - 同步

会阻塞下面代码,返回一个二进制流,可通过toString()转换。

const resulet = fs.readFileSync("./test.js");
console.log(resulet.toString("utf-8"));

promise

promise 版本。

const fs2 = require("node:fs/promises");
fs2
  .readFile("./test.js")
  .then((resulet) => {
    console.log(resulet.toString("utf-8"));
  })
  .catch((err) => {});

可读流 - createReadStream

可以将文件以二进制流的形式读取,通过监听该文件流对应事件来获取文件数据。

适用于处理大文件。

const readSreanm = fs.createReadStream("./test.js");
readSreanm.on("data", (chunk) => {
  console.log(chunk.toString());
});
readSreanm.on("end", () => {
  console.log("end");
});

创建文件

创建文件夹。

fs.mkdirSync("./ff"); // 创建一个文件夹
// 创建多层文件夹
fs.mkdirSync("./ff/f1/f2", {
  recursive: true,
});

删除文件

删除文件夹。

// 删除文件夹(如果该文件夹内还有其他文件会报错)
fs.rmSync("./ff");
// 递归删除文件夹底下的的所有子目录
fs.rmSync("./ff", {
  recursive: true,
});

重命名文件

fs.renameSync("./index.txt", "./index2.txt");

监听文件变化

文件有修改时触发。

大部分构建工具的热更新原理。

fs.watch("./index.txt", (event, filename) => {
  console.log(event, filename);
});

写入文件

fs.writeFileSync("./index.txt", "write cnotent", {
  flag: "a", // 追加内容在文件中,如果没有该文件则覆盖,不然默认覆盖
});

fs.appendFileSync("./index.txt", "js");
  • r:以读取模式打开文件。如果文件不存在,则抛出异常。
  • r+:以读写模式打开文件。如果文件不存在,则抛出异常。
  • w:以写入模式打开文件。如果文件不存在,则创建文件;如果文件存在,则截断文件。
  • w+:以读写模式打开文件。如果文件不存在,则创建文件;如果文件存在,则截断文件。
  • a:以追加模式打开文件。如果文件不存在,则创建文件。
  • a+:以读和追加模式打开文件。如果文件不存在,则创建文件

可写流

适用于大量的数据分批插入

const writeStream = fs.createWriteStream("./index.txt");
const verse = ["ddd", "dddd", "dddddd", "dddddddd"];
verse.forEach((item) => {
  writeStream.write(item + "\n");
});
writeStream.end();

writeStream.on("finish", () => {
  console.log("写入完成");
});

软硬链接

硬链接

链接双方共享一个内存地址。用于共享文件,备份文件。(引用类型)

fs.linkSync("./index.txt", "./index2.txt"); // 原始地址,硬链接之后地址
  • 硬链接会创建一个新的文件,与原始地址文件同步
  • 原始地址文件删除并不影响硬链接文件

软链接(符号链接)

注意:需开启管理员权限

链接文件相当于一个原始文件的快捷方式

fs.symlinkSync("./index.txt", "./index3.txt");

应用

pnpm 的底层原理就是通过软硬链接实现的。

源码分析

通过 libuv(c++)的对uv_fs_t的封装,也就是将 fs 的参数透传给 libuv 层。

// - loop:事件循环对象,用于处理异步操作
// - req:文件系统请求对象,用于保存操作的状态和结果
// - path:要创建的目录的路径
// - mode:目标权限模式
// - cb:操作完成后的回调函数
int uv_fs_mkdir(uv_loop_t* loop,
                uv_fs_t* req,
                const char* path,
                int mode,
                uv_fs_cb cb) {
  INIT(MKDIR);
  PATH;
  req->mode = mode;
  if (cb != NULL)
    if (uv__iou_fs_mkdir(loop, req))
      return 0;
  POST;
}

与事件循环的关系

fs 的 IO 操作都是由 libuv 完成的,它会等到本轮事件循环结束,并且完成任务之后才会将其推入 v8 的事件队列中。

事件循环

JS 的代码分为同步任务和异步任务:

同步任务:在主线程上立即执行的任务,按照代码的顺序依次执行;
异步任务:不进入主线程,进入任务队列。只有任务队列通知主线程执行某个异步任务,该异步任务才会进入主线程

异步任务又分为宏任务和微任务,任务队列又分为宏任务队列微任务队列

宏任务:主代码块(script)、setTimeout、setInterval、setImmediate、I/O、UI rendering,requestAnimationFrame 等;
微任务:Promise.then(async)、MutationObserver、process.nextTick、Object.observe 等;

注意:为方便总结,我将同步任务(主代码块 script)归结到宏任务中,这样就可以以宏任务开启事件循环。

  • 执行一个宏任务(初始是 script,而后是从任务队列中获取);

    script 的执行本身是一个宏任务,是事件循环的第一个任务。

  • 执行过程中如果遇到微任务,就将它添加到微任务队列中,遇到宏任务就将其放入宏任务队列中;
  • 当前宏任务执行完毕后,立即依次执行当前微任务队列中所有的微任务;
  • 在执行微任务时,如果遇到了宏任务,会将其放入宏任务队列中,并继续执行微任务队列中的微任务;
  • 执行完微任务后,执行 UI 渲染,再执行下一个宏任务,重复以上过程。

此为 js 的事件循环机制。以宏任务开始一个事件循环,当所有的微任务执行完毕后(包括执行时微任务时又加入的微任务),再去执行下一个宏任务。

初始示例:

  • 主线程:(script:第一个宏任务,同步执行)
  • 宏任务:script(给主线程)
  • 微任务:null
console.log("script-open");
console.log("1");
setTimeout(() => {
  console.log("9-macro");
  Promise.resolve("10").then((res) => {
    console.log(res);
  });
}, 0);
new Promise((resolve, reject) => {
  console.log("2");
  resolve("4");
})
  .then((res) => {
    console.log(res);
    setTimeout(() => {
      console.log("11-macro");
    }, 0);
    return Promise.resolve("7");
  })
  .then((res) => {
    console.log(res);

    return Promise.resolve("8");
  })
  .then((res) => {
    console.log(res);
  });

new Promise((resolve, reject) => {
  console.log("3");
  resolve("5");
}).then((res) => {
  console.log(res);
});

const el = document.querySelector("#app");
const observer = new MutationObserver((mutationList, observer) => {
  console.log("6-observer", mutationList);
});
observer.observe(el, { subtree: true, childList: true });

// 1 2 3 4 5 6-observer 7 8 9-macro 10 11-macro

Vue 与事件循环

Vue 中的一些语法和钩子函数与事件循环的关系。

  • 创建阶段(setup、create):同步任务(script);
  • onMounted:第一个微任务;
  • nextTick:下一轮事件循环(下一个 tick)的微任务队首;

    例如:如果和 onMounted 在同一层,则在 onMounted 后的一个事件循环的队首。

  • watchEffect:同步任务(script);
微前端

微前端是一种前端架构模式,它将 Web 应用程序拆分为一组小型、可独立开发和部署的模块,每个模块可以由不同的团队开发和维护。

优势:模块化开发(可维护性和可扩展性),团队自治,技术栈无关,可复用性。

问题:技术复杂,适合项目规模大的应用,性能问题,跨域问题,资源隔离和共享问题。

架构模式

微前端的架构模式可以分为两种:基于浏览器端集成基于服务器端集成

基于浏览器集成

通常采用以下方式:

  • Web Components 实现模块化:Web Components 是一组浏览器标准,包括 Custom Elements、Shadow DOM 和 HTML Templates 等,可以实现 Web 应用程序的模块化开发和封装。
  • JS 框架集成:使用 JS 框架(Vue,React 等)通过组件化和路由等技术,将应用程序拆分为小型模块。
  • HTTP 动态加载模块:实现模块的按需加载和更新,从而提高 Web 应用程序的性能和响应速度。

基于服务器

  • 反向代理路由和负载均衡:使用反向代理(如 Nginx、Apache 等)实现 Web 应用程序的路由和负载均衡,可以将请求分发到不同的后端服务和前端模块,从而实现微前端的集成和部署。
  • 服务网格实现模块间通信:使用服务网格(如 Istio、Linkerd 等)实现微前端模块之间的通信和集成,可以实现微服务架构中的服务发现、负载均衡、故障恢复等功能,从而提高微前端应用程序的可靠性和可扩展性。

基于服务器端集成的微前端架构模式相对于基于浏览器端集成的模式,更适用于大型 Web 应用程序,可以提供更好的可靠性和可扩展性。但也会带来一些开销和复杂度,需要开发团队具备一定的技术实力和经验。

child_process

Nodejs 核心 API,可用于处理 cpu 密集型应用

api

  • exec异步方法。一般会有一个回调函数,返回 buffer。

    可用于执行 shell 命令,或跟软件进行交互

  • execSync同步的方法

exec/execSync

执行指定命令

exec('node-v', (err, stdout, stderr) => {
  if (err){
    retunr err
  }
})

execSync('mkdir test') // 执行命令
execSync('start chrome http://www.baidu.com') // 软件交互
  • 适用于执行较小的 shell 命令;
  • 想要立即拿到结果的 shell 可以用 execSync;
  • 字节上限 200kb,超出则报错

spawn/spawnSync

执行指定命令

const netInfo = execSync("netstat");
console.log(netInfo); // 卡住,数量太大

const { stdout } = spawn("netstat", ["-a"]);
stdout.on("data", (msg) => {
  console.log(msg.toString());
});
stdout.on("close", (msg) => {
  console.log("end");
});
  • 没有字节上限,返回的是流
  • 实时返回
  • 有结束事件

_更多配置项_:

const { stdout } = spawn("netstat", ["-a"], {
  cwd: "", // string 子进程的当前工作目录
  env: "", // object 环境变量键值对
  encodingL: "", // string 默认为'utf8'.
  shell: "", // string 用于执行命令的 she11, 在 UNIX 上默认为'/bin/sh',在 windows 上默认为 process.env.ComSpec。
  timeout: "", // 超时时间,默认为 0
  maxBuffer: "", //stdout 或 stderr 允许的最大字节数。默认为 288*1024.。如果超过限制,则子进程会被终止
  killSignal: "", // string|integer 默认为'SIGTERM'
  uid: "", // 设置该进程的用户标识。
  gid: "", // 设置该进程的组标识。
});

execFile/execFileSync

执行可执行文件

bat.sh(mac、linux)

bat.cmd(windows)

// bat.cmd
echo 'start'
mkdir test
cd ./test
echo console.log('test bar') >test.js
echo 'end'
execFile(path.resolve(__dirname, "./bat.cmd"), null, (err, stdout) => {});

底层实现顺序

exec -> execFile -> spawn

fork

只接受 js 模块,为 js 创建子进程

注意:web worker 是浏览器的,而 fork 是 node 的。

const childProcess = fork("./child.js");
childProcess.sent("hello,我是主进程");

// child.js
process.on("message", (msg) => {
  console.log("收到消息了", msg);
});

原理:底层通过 IPC 去通讯,IPC 又基于 libuv 实现,libuv 会根据不同的操作系统调用不同的命令实现。(windows:named pipe | posix: unix domain socket)

event

Nodejs 核心 API,常用于异步事件驱动采用发布订阅模式

发布订阅模式:_当有新的消息时,发布者会将其发布到一个调度中心,而调度中心会将这个消息发送给所有订阅这个消息的订阅者_。

使用

用法跟大多数 emit/on 差不多。(event bus、mitt 等)

const eventEmitter = require("events");

const bus = new eventEmitter();
// 事件默认只能监听(on) 10 个,但是可以手动设置
bus.setMaxListeners(20);
console.log(bus.getMaxListeners();)

const fn = (name) => {
  console.log(name);
};
// 订阅一个事件
bus.on("test", fn);

// 只触发一次
bus.once("test", fn);

// 取消订阅(需传入相同的函数 fn)
bus.off("test", fn);

// 发布一个事件
bus.emit("test", "fxh");

应用

process

在 node 初始化的时候调用了 setupProcessObject(),这个函数内部会读取 process 原型对象(getPrototypeOf),然后将 eventEmitter 的原型嫁接到这个原型对象上(setPrototypeOf),所以 process 也可以使用 on、emit 等。

os

Nodejs os 模块可以跟操作系统进行交互。

api

platform

获取当前操作系统平台。

const os = require("node:os");

console.log(os.platform()); // win32(win32、windows、darwin(mac)、linux)
//

release

获取当前操作系统版本号。

type

获取当前操作系统名称。

比 platform 要获取的信息多一些。

version

获取当前操作系统版本。

homedir

获取当前用户目录。

原理:window:echo %userprofile%
mac:$HOME

arch

获取CPU 架构

const os = require("node:os");

console.log(os.arch()); // x64(arm、arm64、ia32、mips、mipsel、ppc、ppc64、s390、s390x、x64)
//

cups

获取操作系统线程 cpu 信息。

原理:读取操作系统 cpu 的数量。(任务管理器可查)

networkInterfaces

获取网络状态信息。(ip 地址、子网掩码、ip 版本、mac 地址、ip 地址段)

应用

构建工具热更新后自动打开浏览器(open:true)

原理:使用 os 判断不同的操作系统,分别调用对应的 shell 命令。

const os = require("node:os");
const { exec } = require("child_process"); // exec:执行 shell 命令

const platform = os.platform();

const open = (url) => {
  if (platform === "darwin") {
    exec(`open ${url}`); // mac
  } else if (platform === "win32") {
    exec(`start ${url}`); // windows
  } else if (platform === "linux") {
    exec(`xdg-open ${url}`); // linux
  }
};
path

前言

path 在不同的操作系统是有差异的。

posix(unix,likeunix,linux,macOs,windows wsl)

可移植操作系统接口,这是一套标准,为了保证不同遵循它的操作系统的可移植性。

例如:启动一个进程调用 fork 函数,大家都调用这个。

  • 路径分隔符使用正斜杠(/)。

windows

不遵守 posix。

  • 路径分隔符使用反斜杠(\)。

api

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

basename

返回指定路径的最后一个(文件/文件夹)名

const path = require("node:path");

console.1og(path.basename('/foo/bar/baz/asdf/xm.html')) // xm.html
console.1og(path.basename('\\foo\\bar\\baz\\asdf\\xm.html')) // xm.html
console.1og(path.posix.basename('/foo/bar/baz/asdf/xm.html')) // ×
console.1og(path.win32.basename('/foo/bar/baz/asdf/xm.html')) // xm.html

注意:winddows 兼容正反斜杆写法,posix 不兼容。

dirname

返回指定路径的目录(不含最后一个(文件/文件夹)名。)。

console.1og(path.dirname('/foo/bar/baz/asdf/xm.html')) // ../foo/bar/baz/asdf

extname

返回路径的扩展名。如果是文件夹则返回为空。

join

拼接路径。支持操作符。

../。栈

resolve

解析路径,返回绝对路径。

注意:多个路径返回最后一个。(_根据拼接路径的数量和类型会有差异_)

parse(format)

解析路径,以对象形式。

console.1og(path.parse('/home/file.txt'))
const res = {
  root: '/',
  dir: 'home',
  base: 'file.txt',
  ext: '.txt',
  name: 'file'
}

format(parse)

parse 逆向操作。

sep

返回操作系统的路径分隔符。可用于实现跨平台