前端的全栈Deno篇(五):与前端保持一致的模块化方案,摆脱ERR_REQUIRE_ESM和mjs、cjs等模块混乱带来的心智负担

news/2024/12/26 20:26:39/

在现代JavaScript开发中,模块化系统的演变经历了多次变革,使得前端和后端开发人员在选择模块加载方式时常常感到困惑。尤其是Node.js所采用的CommonJS和ESM(ECMAScript Modules)两种模块体系,以及文件扩展名的多样性(如.cjs.mjs.js)带来的复杂性和混乱。Deno的诞生以及其2.0版本带来的模块化系统,正试图解决这些问题,并为开发者提供一种更统一、符合Web标准的模块化方案。

写这篇文章的原因就是,今天使用llamaindex,被其混乱的第三方模块折腾麻了,本来只是简单测试,后面不得不改了模块类型,都是因为cjs和ejs混用导致的,而且 cjs和mjs模块的导入需要加后缀!简直不可忍受

Node.js模块化系统的演变与挑战

Node.js最早采用的模块化方案是CommonJS,这是为了让开发者能够在Node环境中编写模块化代码而提出的方案。通过require()方法,CommonJS实现了模块的导入与导出。然而,这种模块化方式是基于服务器端环境而设计的,与浏览器中的模块加载机制存在较大差异。

随着ES6(也称为ES2015)的发布,JavaScript正式引入了标准化的模块化机制——ESM(ECMAScript Modules)。ESM通过importexport语法,使得模块化变得更加规范化和易于理解。然而,由于Node.js在最初阶段只支持CommonJS,导致这两种模块化方案在Node.js中长期共存。

在Node.js中,当使用ESM模块时,需要使用文件扩展名.mjs或者在package.json中指定"type": "module",而CommonJS模块通常使用.cjs扩展名或默认的.js扩展名。这种双重模块系统的存在,给开发者带来了很多困扰。例如,如果试图在CommonJS模块中使用import语法,或者在ESM模块中使用require(),往往会遇到类似ERR_REQUIRE_ESM的错误。

此外,Node.js对文件扩展名的处理也较为复杂,开发者需要记住何时需要显式指定文件扩展名,何时可以省略,这增加了代码的维护成本和理解难度。这种模块化系统的混乱现状,让很多开发者在使用第三方库、集成旧代码时遇到许多阻碍。

Node.js中的模块化代码示例

以下是Node.js中使用CommonJS和ESM的示例:

CommonJS示例(require()方式):

// commonjs-module.js
module.exports = function() {console.log("Hello from CommonJS");
};
// main.js (CommonJS)
const greet = require("./commonjs-module");
greet();

CommonJS示例(导入导出对象):

// commonjs-object-module.cjs
const greetings = {greetEnglish: function() {console.log("Hello from CommonJS");},greetSpanish: function() {console.log("Hola desde CommonJS");}
};module.exports = greetings;
// main.cjs (CommonJS)
const greetings = require("./commonjs-object-module.cjs"); // 一定要加.cjs后缀
greetings.greetEnglish();
greetings.greetSpanish();

ESM示例(import方式):

// esm-module.mjs
export function greet() {console.log("Hello from ESM");
}
// main.mjs
import { greet } from "./esm-module.mjs";
greet();

在Node.js中,必须根据模块类型选择适当的加载方式,否则可能会遇到各种错误。

Deno_74">Deno的统一模块化方案

为了解决这些痛点,Deno引入了一种更加现代化、统一的模块化系统,并且严格遵循Web标准。Deno最初的设计目标之一就是摆脱Node.js的历史包袱,避免其模块化系统中的种种复杂性。

Web标准的模块化方式

Deno采用了原生的ESM作为其模块系统,彻底抛弃了CommonJS,这意味着开发者在Deno中编写模块时,只需使用标准的importexport语法,无需担心CommonJS和ESM之间的兼容问题。这样一来,代码的可读性和可维护性得到了极大提升。

此外,Deno要求模块必须通过URL或相对路径来加载,并且所有的模块文件都需要明确指定扩展名(例如.js.ts等)。这种设计与浏览器的模块加载方式保持了一致,使得在Deno中编写的代码可以很方便地移植到浏览器环境中,而无需进行额外的改动。

插一嘴,如果没有写扩展名,默认是js - 这时候可以绕过一些检测机制,例如针对文本的加密等

Deno还内置了对TypeScript的支持,开发者可以直接编写TypeScript代码,而不需要额外的编译步骤。这种内置支持使得TypeScript的使用更加自然,并且与ESM的模块化机制无缝集成。

Deno_88">Deno中的模块化代码示例

以下是Deno中使用ESM的示例:

使用ESM的Deno示例:

// greet.ts
export function greet() {console.log("Hello from Deno");
}
// main.ts
import { greet } from "./greet.ts";
greet();

Deno中,模块的加载方式与浏览器类似,文件扩展名必须明确指定。这使得代码更加直观,也更符合开发者的预期。

模块管理的现代化

与Node.js依赖npm来管理模块不同,Deno没有中央的包管理工具,而是采用URL来直接引入第三方模块。这种方式借鉴了Web的资源加载方式,使得模块的获取过程更加透明和简单,开发者可以直接通过URL查看模块的源代码。这种模块管理方式避免了传统包管理器中的“依赖地狱”,并且使得项目的依赖关系更加清晰。

以下是Deno中通过URL加载第三方模块的示例:

import { serve } from "https://deno.land/std@0.113.0/http/server.ts";const handler = (request: Request): Response => {return new Response("Hello from Deno server!", { status: 200 });
};serve(handler);

通过URL加载模块的方式,使得开发者可以清楚地看到模块的来源,并且减少了对包管理器的依赖。

此外,Deno 2.0引入了deno.json配置文件,使得开发者可以更加灵活地配置项目,类似于Node.js中的package.json,但更加简洁和统一。通过这种配置文件,开发者可以指定TypeScript编译选项、模块路径别名等,从而进一步提高开发体验。

Deno_129">Deno模块化方案的优势

  1. 统一的模块化标准Deno彻底抛弃了CommonJS,只支持ESM模块,这使得模块化体系变得简单一致,开发者不再需要在CommonJS和ESM之间切换,也避免了常见的ERR_REQUIRE_ESM错误。

  2. 与Web兼容Deno的模块加载方式与浏览器保持一致,使用URL或相对路径加载模块,并且必须显式指定文件扩展名。这种方式使得代码在Deno和浏览器之间的互操作性更强,减少了跨环境迁移的障碍。

  3. 内置TypeScript支持Deno直接支持TypeScript,无需额外的配置或编译工具,这使得现代JavaScript开发更加方便,开发者可以充分利用TypeScript的类型系统来提高代码的可靠性。

  4. 模块管理简化:通过直接使用URL加载模块,Deno减少了对包管理器的依赖,避免了包版本冲突等问题,同时也让模块的来源变得更加透明。

总结

Node.js的模块化系统在历史上经历了从CommonJS到ESM的演变,但这种演变带来的兼容性问题和复杂的文件扩展名规则,给开发者带来了很多困扰。而Deno通过采用统一的ESM模块化方案,严格遵循Web标准,彻底解决了这些问题。Deno的2.0版本更是通过deno.json等配置进一步提升了开发者的体验。

对于前端全栈开发者来说,Deno提供了一种现代化、简洁而统一的开发体验,不再需要为模块化的复杂性而烦恼。它让开发者能够更加专注于代码本身,而不是被各种历史遗留的模块化问题所困扰。如果你还在为Node.js中的模块化混乱而烦恼,不妨尝试一下Deno,相信它会为你带来耳目一新的体验。


http://www.ppmy.cn/news/1544331.html

相关文章

15分钟学 Go 第 32 天:基本调试技巧

第32天:基本调试技巧 在Go语言的开发过程中,调试是确保代码质量的重要环节。通过有效的调试技巧和工具,开发者可以快速定位和修复问题,从而提高开发效率。今天的学习目标是掌握Go语言中的基本调试工具和技巧。 1. 调试的必要性 …

笔记整理—linux驱动开发部分(5)linux设备驱动模型

类(class)、总线(bus)、设备(device)、驱动(driver)这四个结构体可以实例化从而形成驱动模型。 kobject是抽象的内核对象的父类。对象的生命周期是在kobject中有相关机制实现的。 sy…

ElasticSearch - Bucket Script 使用指南

文章目录 官方文档Bucket Script 官文1. 什么是 ElasticSearch 中的 Bucket Script?2. 适用场景3. Bucket Script 的基本结构4. 关键参数详解5. 示例官方示例:计算每月 T 恤销售额占总销售额的比率百分比示例计算:点击率 (CTR) 6. 注意事项与…

Python代码解析:处理JSON数据并导入Neo4j数据库

Python代码解析:处理JSON数据并导入Neo4j数据库 引言代码结构概览代码详解1. 导入必要的库2. 定义数据文件路径3. 读取JSON文件内容4. 连接Neo4j服务器5. 遍历数据并处理 总结参考资料 引言 在数据处理和分析中,JSON是一种常见的数据格式。Neo4j是一个高…

智慧用电监控装置:引领0.4kV安全用电新时代

在智能科技日新月异的今天,电力安全与管理正迎来一场前所未有的革新。为0.4kV以下TT、TN系统打造的智慧用电在线监控装置不仅重新定义了电力监控的边界,更为建筑安全用电筑起了一道坚不可摧的防线。 装置集成了单、三相交流电精确测量、四象限电能计量、…

详解Envoy

23-1-Envoy 你觉得为什么它能击败Nginx以及HAProxy等竞品,成为Service Mesh体系核心? Envoy 是由 Lyft 开发并开源的一款高性能开源代理,近年来迅速成为 Service Mesh 体系的核心组件之一。它之所以能够击败 Nginx 和 HAProxy 等竞品&#xf…

进程守护SuperVisord内部的进程定时监测并重启

一个swoole的wensocket程序运行在SuperVisord下端口9503 设置一个每分钟任务监测9503的端口链接数,输出链接数,并在链接数为0的情况下重启wensocket进程。 以下截图是宝塔面板环境下 #!/bin/bash current$(date %H.%M) ws9503_procnumnetstat -nat | gre…

2024年网鼎杯朱雀组|MISC

1.MISC01 文件打开一堆01,差分曼彻斯特 from libnum import * f open("data","r",encoding"utf-8") all_str f.read() f.close() out "" n (len(all_str) // 2) - 1 for i in range(0, n):tmp1 all_str[i*2:i*22]tmp…