首页 > 编程语言 >关于 NodeJS 模块化不得不说的坑

关于 NodeJS 模块化不得不说的坑

时间:2022-10-06 00:33:36浏览次数:89  
标签:node NodeJS 模块化 js CJS import ESM foo 不得不

关于 NodeJS 模块化不得不说的坑

本文写于:2022-10-05

在过去的几年时间里,我一直是一名全栈工程师,工作内容偏向于前端。但是在后端技术栈上,我其实一直没有太多的接触 Node.js,更多的是使用 Golang、Ruby 进行后台的编写。

最近跳槽,接手了一个使用 Node.js 开发的后端项目,因此这个国庆期间就进行了一番学习与折腾。

不折腾不要紧,一折腾血压就高的不行——Node 在一个简单的模块化问题上,居然有这么多坑。

CJS 与 ESM 的简单介绍

Node 发展到了今天,基本由两种模块化统治:CommonJS 与 ES Module。

// CJS
const fs = require("node:fs");

module.exports = { foo: 1, bar: 2 };

// ESM
import fs from "node:fs";

export const foo = 1;

export const bar = 2;

二者孰优孰略呢?简单来说:CommonJS 是曾经的老大,ES Module 是未来的方向。

面临的问题

现如今绝大部分 Node.js 的第三方库都是由 CJS 写成的——这是因为 Node 曾经很长一段时间里不支持 ESM。

到了 2022 年,这对于前端来说其实根本不是问题了,因为 webpack、esbuild、rollup 之类的打包工具是非常强大的。不管你是什么模块化规范,只要用了打包工具,都给我统统 bundle 进来!(所谓 bundle,其实就是打包工具可以将自己写的 n 个文件的代码、第三方的 n 个库,都编译输出到一起,比如全放到一个文件里。

可是后端项目不能乱 bundle 啊,具体为什么可以参考:https://github.com/ZenSoftware/bundled-nest。

理由我大概总结一下:很多第三方库会使用 native extension,比如 C++ Addons,这些是不跨平台的,必须到了目标平台再 build,如果把 dependencies 都 bundle 起来,对于 Node.js 项目来说很容易出现问题

问题 1:如何交叉引入(ESM 引入 CJS、CJS 引入 ESM)

所以我们现在就面临了第一个问题:在打包工具不能参与的情况下,第三方库又可能是 CJS 规范、又可能是 ESM 规范,我们该如何处理呢?

通常,一些成熟的项目都会有两套代码:一套在你 require 它的时候生效,使用 CJS 规范;另一套使用 ESM 规范,在你 import 他的时候生效。

就像这样:

- foo.cjs
- foo.mjs

可有的项目他很“叛逆”,或者用户量不多,所以写了其中一种规范。比如坑爹的 node-fetch@3,彻底放弃了对 CJS 的支持,也就是禁止你 require 引入它了。

对于 CJS 的第三方库规范来说,ESM 对其支持还是可以的。

你可以比较正常的 import CJS 暴露出来的模块。

// lib.cjs
module.exports = function sayHello() {};

// main.js
import sayHello from "./lib.cjs";

唯一有点问题就是不能随便在 import 的时候进行析构赋值:

// lib.cjs
module.exports = {
  a: 1,
  b: 2,
};

// main.js
import { a, b } from "./lib.cjs"; // 报错

import lib from "./lib.cjs"; // 成功

const { a, b } = lib;
console.log(a, b);

这是因为 ESM 是后出的,必须考虑到需要兼容 CJS 的情况。

但 CJS 导入 ESM 就没这么好运了,非常困难。

目前能做到的比较好的方法就是使用 dynamic import。

// lib.mjs
export default function sayHello() {}

// main.js
import("./lib.mjs").then((lib) => {
  lib.default();
});

首先这要求 node 的版本支持 dynamic import,其次他只能是异步的导入,对我们很多代码书写来说是存在问题的。

这其实是一个很大的问题:新的第三方库都必须想办法兼容 CJS,不然的话很多老项目就没办法使用你了。这就大幅度拖慢了 ESM 统一的节奏。

问题 2:ESM 必须带上文件扩展名进行 import

在 CJS 规范中,我们 require JS 文件是不需要写扩展名的。

const foo = require("./foo");

可 ESM 不行,因为 Node 认为你不止可以 import JS 文件,所以没有默认解析其为 .js 的能力。

这可麻烦大了。

因为现在 TypeScript 如日中天,非常好用。我们的很多 Node 项目都是 TS 写好了之后,tsc 编译成 JS 再来跑的。

但 TS 里面你 import 是不需要扩展名的——甚至写了 .ts 的扩展名还会报错。

import foo from "./foo.ts"; // 报错

因此,tsc 编译出来的文件也是没有扩展名的:

// main.ts
import foo from "./foo";

// main.js
import foo from "./foo"; // ESM 下报错

为了解决这个问题,Node 提供了一个 flag: --es-module-specifier-resolution=node

只需要运行 node --es-module-specifier-resolution=node main.js 就可以使得 import 不需要扩展名。只是很遗憾,这个功能还是一个实验性功能,随时可能会在新版本中移除。

总结

对于大部分 Node 项目来说,可以这么解决模块化的坑:

  1. 使用 ESM
  2. package.json 中 type 字段设为 module
  3. 对只有 commonjs 的包(比如 lodash)谨慎进行 import 析构
  4. 如果是由 tsc 编译出来的,import 不具备扩展名,使用 node --es-module-specifier-resolution=node dist/main.js 进行启动

如果使用 nestjs 这类拥有自己 cli 工具的项目,可以查阅文档如何为 node 启动添加参数,例如 nestjs 可以进行如下改写:

{
  "start:dev": "nest start --watch",
  "start:dev:esm": "nest start --watch  -e 'node --es-module-specifier-resolution=node'",
  "start:prod": "node dist/main",
  "start:prod:esm": "node --es-module-specifier-resolution=node dist/main"
}

(完)

标签:node,NodeJS,模块化,js,CJS,import,ESM,foo,不得不
From: https://www.cnblogs.com/xhyccc/p/16756870.html

相关文章

  • 【nodejs开发】nodejs实现socket网络通信
    (本节内容如下:)1、简介在NodeJS中有三种socket:1.TCP,2.UDP,3.Unix域套接字。UDP/datagramsocketsClass:dgram.SocketEvent:'close'Event:'connect'Event:'erro......
  • 前端模块化-Day43
    前端模块化:发展史:①全局函数模式:将不同的函数功能封装成不同的全局函数。(调用时会导致修改覆盖)//全局函数模式:将不同的功能封装成不同的全局函数letmsg='modul......
  • redux模块拆分——start状态模块化——connect高阶函数模块化——Action函数返回对象
    redux模块拆分使用redux库中提供的​​combineReducers​​方法,可以将多个拆分reducer函数合并成统一的reducer函数,返回一个新reducer,提供给createStore来使用。import{co......
  • 配置nodejs
    --下载左侧ba长期维护版--点击安装--右键管理员打开cmd--输入 node-v--输入npm-v--默认缓存在C盘,修改默认路径--打开安装文件夹--新建node_global 和 ......
  • Nodejs Express Mysql 增删改查
    constmysql=require('mysql2')//注意是mysql2,不是mysql。mysql2支持mysql8.0以上的加密方式constdb=mysql.createPool({host:'127.0.0.1',user:'ro......
  • 使用 NodeJS、Typescript 和 tsyringe 实现依赖倒置
    使用NodeJS、Typescript和tsyringe实现依赖倒置依赖倒置是5个SOLID原则之一,在我看来,也是最重要的原则之一,因为它允许通过抽象而不是使用具体实现来解耦模块。记......
  • windows设置pm2开机服务 自启动nodejs项目
    PM2是带有内置负载平衡器的Node.js应用程序的生产过程管理器。可以利用它来简化很多Node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。安装部署1、我们一......
  • 为什么说数字化转型是国内企业不得不走的突围向上之路?
    数字化既是信息化的产物,也是信息化的演进阶段之一,更是构建智慧企业的首要前提。在数字化转型大潮中,企业如逆水行舟,不进则退。如果不进行数字化转型,那么企业将会被用户抛弃......
  • 带你了解NodeJs的模块系统
    前言在JavaScript语言中,两个独立的js脚本互相引用是无法实现的,只能在Html页面中引入多个脚本来做到关联。NodeJs提供了一个简单的模块系统,它让Js代码之间可以互相引用,方便暴......
  • 基于C6748 DSP+FPGA MIMO雷达验证系统模块化设计与实现
    MIMO雷达和传统雷达不同,因为其本身特有的优点,使得这些年很多科研人员对其进行研究。MIMO雷达的优点是能够不受天线大小的束缚,有着很高的方位分辨率。实现了MIMO雷达验证......