JavaScript 没有“包”
前言
除了古老的 C/C++,几乎所有的编程语言都有模块系统,都有官方的包管理器。我们一般不自己实现所有的代码,实际应用开发过程中大量使用开源库和框架。这篇文章演示了如何把自己实现的库变成一个包,一个包就是你的应用,也是你的库。
随着程序越来越大,我们会将不同用途的代码放到不同的源文件。为了代码共享,我们会将部分代码提出来作为一个库。如果我们的项目越来越复杂的话,就会既有库又有可执行程序。如何组织项目的代码,如何理解一个复杂项目的代码结构。只需要掌握两点:
- 理解语言本身的模块或包的机制
- 理解包管理器或构建系统如何构建库/程序
JavaScript 语言本身没有包
JavaScript 的包不是一个语言层面的概念,是包管理器层面的概念。换句话说,JavaScript 语言本身没有包,包是 npm 的特性,让你构建、测试、分享 JS 模块。
JavaScript 只有模块,一个 .js 文件就是一个 JavaScript 模块。
JavaScript 的模块相当于是 Go 语言里面的包,只不过 Go 语言的包可以是单个目录下的多个 .go 文件。
Go语言的代码通过包(package)组织,包类似于其它语言里的库 (libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个
.go源代码文件组成, 目录定义包的作用。每个源文件都以一条package声明语句开始,这个例子里就 是package main, 表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在 这个文件里的程序语句。
npm 包
BTW:Rust 也和 JavaScript 一样,Rust 语言本身没有包的概念,Rust 语言本身只有 Crate 和 Module。Rust 的 Module 是命名空间也是把代码分离到不同的源文件。Cargo 的包只能有一个 Library Crate,可以有多个 Binary Crate。rustc 一次考虑一个 crate。
node 一次考虑一个 JS 模块,node script.js 运行一个 JS 模块。npm 包只能有一个库,可以有多个可执行脚本。库的名字是 package.json 中的 name,这也是包的名字,"main" 字段是这个库的入口。一个库也是一个包,package.json 描述了一个包。
我们来创建一个包,并使用它。
npm init 创建一个 JavaScript 的包,即创建 package.json。创建 greeting
mkdir greeting
cd greeting
npm init -y
我们要修改 package.json,type: module 告诉 NodeJS 这个包的 JS 文件都是 ES 模块。greeting 的用户要使用 import 来导入包或者模块就必须做这个修改。
--- i/package.json
+++ w/package.json
@@ -2,6 +2,7 @@
"name": "greeting",
"version": "1.0.0",
"main": "index.js",
+ "type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
main: index.js 是这个包的入口,我们要创建这个 index.js。index.js 是默认的 main,我们可以自定义 main。
// Filename: index.js
export function hello(name) {
return `Hi, ${name}. Welcome!`
}
这样我们就建立好了一个 JavaScript 的包,这个包提供一个 hello 函数。
创建一个名为 hello 的包,使用 greeting 这个包。
mkdir hello
cd hello
npm init -y
创建 hello 包后目录结构如下
<home>/
├── greeting
│ ├── index.js
│ └── package.json
└── hello
└── package.json
使用 greeting,我们就要安装这个包,在 hello 文件夹下执行:
npm i ../greeting
安装 greeting 包,npm 创建了一个 node_modules 目录,把 greeting 的代码放到了 node_modules。因为这是一个本地的包,npm 创建了一个符号链接,指向了 greeting 目录。
node_modules/
└── greeting -> ../../greeting
我们还没有在 hello 这个包里面写任何的代码,我们在 hello 这个包使用 greeting 提供的 hello 函数。
// Filename: hello/index.js
import { hello } from "greeting"
const message = hello("Mikami Yua")
console.log(message)
然后我们运行 hello 下的 index.js。
$ node index.js
(node:2544) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///home/user/hello/index.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /home/user/hello/package.json.
(Use `node --trace-warnings ...` to show where the warning was created)
Hi, Mikami Yua. Welcome!
index.js 就是我们 hello 程序的入口,NodeJS 会自动找到 JS 模块引用的其他 js 模块。
这里有一个警告,提示我们要消除这个警告就在 hello/package.json 中加上 "type": "module"。NodeJS 默认使用 CommonJS,CommonJS 失败后会尝试 ES module。
npm 不仅仅是包管理器,也是包的构建工具的前端。npm build 构建这个项目,npm install 安装项目依赖。背后可能是调用 esbuild 或者其他的工具。
一个 package 就相当于是一个库,你可以引入库的某一个模块,所有的 JS 文件都是一个模块。你肯定不希望所有的 JS 文件都是公开的,有一部分代码是库内部使用的,不是 API,将来可能会改变文件的目录结构,甚至删除部分内部的函数。package.json 还有一个 "exports" 字段,显式声明这个包的哪些模块是公开的。
我们修改greeting包,使用 "exports",现代的 JS 项目推荐使用
--- i/package.json
+++ w/package.json
@@ -1,11 +1,11 @@
{
"name": "greeting",
"version": "1.0.0",
- "main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
+ "exports": "./index.js",
"keywords": [],
"author": "",
"license": "ISC",
把 index.js 中的 hello 函数移到 hello.js 中去。
// Filename: greeting/index.js
// re-rexport hello
export { hello } from "./hello.js"
// Filename: greeting/hello.js
export function hello(name) {
return `Hi, ${name}. Welcome!`
}
我们改变了 greeting 包的结构,但是仍然提供 hello 函数,greeting 改动后 hello 包的代码不需要做任何改动,还是可以使用 node index.js 运行。
我们作为 greeting 库的作者,知道 hello 函数是 greeting/hello.js 提供的,我要直接从对应的 JS 模块导入 hello 函数。
--- i/index.js
+++ w/index.js
@@ -1,5 +1,5 @@
// Filename: hello/index.js
-import { hello } from "greeting"
+import { hello } from "greeting/hello.js"
const message = hello("Mikami Yua")
console.log(message)
我们运行代码得到了 ERR_PACKAGE_PATH_NOT_EXPORTED 错误,"exports" 限定了有哪些模块是公开的。
$ node index.js
node:internal/modules/esm/resolve:314
return new ERR_PACKAGE_PATH_NOT_EXPORTED(
^
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './hello.js' is not defined by "exports" in /home/user/hello/node_modules/greeting/package.json imported from /home/user/hello/index.js
at exportsNotFound (node:internal/modules/esm/resolve:314:10)
at packageExportsResolve (node:internal/modules/esm/resolve:662:9)
at packageResolve (node:internal/modules/esm/resolve:842:14)
at moduleResolve (node:internal/modules/esm/resolve:926:18)
at defaultResolve (node:internal/modules/esm/resolve:1056:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:654:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:603:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:586:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:242:38)
at ModuleJob._link (node:internal/modules/esm/module_job:135:49) {
code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}
Node.js v22.13.0
如果我们把 exports 从 package.json 移除,那我们就能根据包的目录结构从任意一个模块中导入。
NodeJS 的 ES Module
文档介绍了 Node.js 会把什么东西当作是 ES Module。package.json 的 type 是 "module", Node.js 把输入当作是 ES Moudle。.js 文件内使用了 ES6 Module 的语法,那就是一个 ES Module。
总结
Take away message: JavaScript 本身只有模块,包的概念是 npm 和 Node.js 建立的。package.json 定义了一个 JavaScript 的包。exports 字段指定了这个包的哪些模块是公开的,公开的模块可以被用户导入。
JavaScript 的包管理方式和 Rust 的包管理的方式非常相似,一个包倾向于只是一个库,或者提供多个可执行文件。
阅读材料:
JavaScript 没有“包”的更多相关文章
- javascript 閉包
這兩種寫法都是可以的. 第一種: function a(){ var m=[]; for(var i=1; i<10; i++){ (function(i){ function b(){ con ...
- 构建javascript array包
//像一个数组添加数组. copyArray = function(inSrcArray,inDestArray){ var i; for(i=0;i<inSrcArray.length;i++ ...
- JavaScript包管理器综述
JavaScript包管理器综述 作者:chszs,未经博主同意不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs 对于JavaScript来说.包管理器 ...
- JavaScript资源大全中文版(Awesome最新版--转载自张果老师博客)
JavaScript资源大全中文版(Awesome最新版) 目录 前端MVC 框架和库 包管理器 加载器 打包工具 测试框架 框架 断言 覆盖率 运行器 QA 工具 基于 Node 的 CMS 框 ...
- JavaScript代码模块化的正规方法
RequireJS-CommonJS-AMD-ES6 Import/Export详解 为什么起了一个这个抽象的名字呢,一下子提了四个名词分别是:RequireJS,CommonJS,AMD,ES6,答 ...
- Node.js入门:包结构
JavaScript缺少包结构.CommonJS致力于改变这种现状,于是定义了包的结构规范(http://wiki.commonjs.org/wiki/Packages/1.0 ).而NPM的 ...
- JavaScript资源大全
目录 前端MVC 框架和库 包管理器 加载器 打包工具 测试框架 框架 断言 覆盖率 运行器 QA 工具 基于 Node 的 CMS 框架 模板引擎 数据可视化 编辑器 UI 输入 日历 选择 文件上 ...
- 意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提交的javascript代码! 不敢藏私,特与大家分
最近研发BDC 云开发部署平台的数据路由及服务管理器意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提 ...
- Awesome Javascript(中文翻译版)
[导读]:GitHub 上有一个 Awesome – XXX 系列的资源整理.awesome-javascript 是 sorrycc 发起维护的 JS 资源列表,内容包括:包管理器.加载器.测试框架 ...
- 让你能看懂的 JavaScript 闭包
让你能看懂的 JavaScript 闭包 没有废话,直入主题,先看一段代码: var counter = (function() { var x = 1; return function() { re ...
随机推荐
- Paxos算法:如何解决分布式系统中的共识问题?
背景 Paxos 算法是 Leslie Lamport(莱斯利·兰伯特)在 1990 年提出了一种分布式系统 共识 算法.这也是第一个被证明完备的共识算法(前提是不存在拜占庭将军问题,也就是没有恶意节 ...
- QT5笔记: 25. 非模态的自定义对话框
窗口对象为QDialog 显示方法为 show(); locateCell->show(); 可以通过public方法或者信号槽机制获取非模态窗口的信息 例子:非模态窗口,为主窗口数据输入吧 v ...
- 机器学习 | 强化学习(6) | 策略梯度方法(Policy Gradient Method)
6-策略梯度方法(Policy Gradient Method) 策略梯度概论(Introduction) 基于策略(Policy-Based) 的强化学习 对于上一节课(价值函数拟合)中采用参数\( ...
- 非局域网远程访问MySQL
使用内网穿透解决,市面上说道最多的是"花生壳" 主要操作见这篇官方说明 但其中提到的什么花生棒(第二.三点)完全不用管,应该算是产品推销. 登录后选"新增内网映射&quo ...
- ant design pro git提交error; Angular 团队git提交规范
前言 在使用 ant design pro 时,git 提交报错 > running commit-msg hook: fabric verify-commit ERROR 提交日志不符合规范 ...
- PHP Fatal error: Uncaught RedisException: Redis server went away in
PHP Fatal error: Uncaught RedisException: Redis server went away in 导致这个问题的原因可能有 1.redis未安装,php没有开启r ...
- 使用PowerPoint优雅地更改证件照底色
使用PowerPoint优雅地更改证件照底色 首先我们打开一张空白的演示文稿,并将要修改的证件照进行粘贴.(图片来自窝窝摄影,侵删) 选中图片,点击 格式,再点击 删除背景. 点击标记要保留的区域,对 ...
- The surprising impact of mask-head architecture on novel class segmentation精讲
目录 Mask RCNN Problem Definition Key idea Only Mask Head Code Summary Refer 大家好,这是我今天要讲的论文,它是2021年发表在 ...
- js将 2023-07-13T10:12:23+0800转为 YYYY-MM-DD HH:mm:ss格式
// 封装的日期时间格式化函数 function formatDateTime(dateTimeString) { const inputDate = new Date(dateTimeString) ...
- 探秘Transformer系列之(19)----FlashAttention V2 及升级版本
探秘Transformer系列之(19)----FlashAttention V2 及升级版本 目录 探秘Transformer系列之(19)----FlashAttention V2 及升级版本 0 ...