菜单

模块加载与解析

相关源文件

本文档介绍了 Deno 如何从本地文件、远程 URL、npm 包等各种来源加载和解析模块。它涵盖了模块解析、加载策略以及依赖处理中的关键组件。

有关启动模块加载的 CLI 架构信息,请参阅 CLI 架构

模块解析概述

Deno 中的模块解析是指将模块说明符(如文件路径、URL 或裸说明符)转换为可获取和执行的完全限定 URL 的过程。此过程遵循基于模块上下文和类型的明确定义的规则集。

图:模块解析流程

来源:cli/resolver.rs53-124 cli/graph_util.rs96-121

解析策略

Deno 根据正在导入的模块类型采用多种解析策略。

文件 URL

本地文件导入是相对于导入模块或作为绝对路径进行解析的。

// Examples
import "./foo.ts";         // Relative to current file
import "/absolute/foo.ts"; // Absolute path from filesystem root
import "../utils/bar.ts";  // Relative to parent directory

远程 URL

可以使用完整的 URL 导入远程模块

import "https://deno.land/std/http/server.ts";

远程模块在下载后会被获取、缓存,并与本地模块的处理方式相同。

导入映射

导入映射提供了一种将裸说明符重新映射到不同 URL 的方法。它们在 JSON 文件中定义,并通过 `--import-map` 标志引用,或在 Deno 配置文件中进行引用。

导入映射在解析过程中用于将裸说明符转换为实际 URL。

来源:cli/args/mod.rs542-550 cli/factory.rs187-232

Node.js 解析

为了兼容 Node.js,Deno 支持

  1. npm 包的裸说明符
  2. Node.js 内置模块
  3. Node.js 的 `require()` 解析算法
// Example of Node.js style imports
import fs from "node:fs";
import express from "npm:express";

来源:cli/node.rs20-29 ext/node/ops/require.rs40-52

模块加载管道

解析后,模块会通过一系列步骤进行加载

图:模块加载过程

来源:cli/module_loader.rs145-252 runtime/worker.rs297-313

模块图

Deno 构建所有模块依赖关系的图以

  1. 检测循环依赖
  2. 确保所有依赖项都已正确加载
  3. 实现高效的类型检查
  4. 支持智能缓存

模块图是通过从入口点模块开始,递归解析和分析所有导入来构建的。

模块图验证

执行前,Deno 会验证模块图以确保

  1. 所有模块都可以被解析
  2. 没有导入错误
  3. 类型检查通过(如果已启用)

来源:cli/graph_util.rs92-124 cli/module_loader.rs166-205

缓存

Deno 通过缓存模块来提高性能

  1. 远程模块:获取后缓存于 `DENO_DIR/deps/` 中
  2. 编译后的模块:TypeScript 和 JSX 在编译后进行缓存
  3. npm 包:缓存于 `DENO_DIR/npm/` 中

缓存用于解析模块和存储编译后的输出。

来源:cli/cache.rs

NPM 包解析

Deno 通过 `npm:` 说明符前缀支持 npm 包。解析过程会

  1. 解析 npm 包的需求
  2. 检查包是否已安装
  3. 如果需要,安装包
  4. 将包解析到其在 node_modules 目录中的位置
  5. 解析主入口点或特定子路径
// Examples
import express from "npm:express";
import { parse } from "npm:yaml@2.2.1";
import chalk from "npm:chalk/index.js";

来源:cli/worker.rs380-425 cli/npm/mod.rs46-56

TypeScript 模块解析

Deno 对 TypeScript 模块有特殊处理

  1. TypeScript 文件与 JavaScript 遵循相同的解析规则
  2. 仅用于类型的导入会被解析,但不会包含在运行时模块图中
  3. `.d.ts` 声明文件用于类型检查,但不会在运行时加载
  4. TypeScript 文件在执行前会转译成 JavaScript

来源:cli/module_loader.rs231-248

JSR 包解析

Deno 支持 JSR 注册表(JavaScript Registry)的包

  1. 包通过 `jsr:` 前缀导入
  2. 包版本根据 semver 规则进行解析
  3. JSR 包会被本地缓存
// Example of JSR imports
import { assertEquals } from "jsr:@std/assert@0.218.0";

来源:cli/args/mod.rs75-99

Worker 和模块加载

Deno 中的 Web Workers 拥有自己的模块加载上下文

  1. 每个 worker 都有自己的模块图
  2. 除非另有说明,否则 worker 会继承父级的权限
  3. Worker 模块相对于 worker 的入口点进行解析

来源:runtime/web_worker.rs412-594

处理模块类型

Deno 支持各种模块类型

模块类型文件扩展名处理方式
JavaScript.js, .mjs直接执行
TypeScript.ts, .mts转译为 JS 后执行
JSX/TSX.jsx, .tsx转译为 JS 后执行
JSON.json解析为 ES 模块并公开
WebAssembly.wasm实例化为 WebAssembly.Module

来源:cli/build.rs9-130

锁定文件

Deno 支持锁定文件(deno.lock)以确保确定性的模块解析

  1. 锁定文件会记录远程依赖项的确切版本
  2. 它们会阻止远程模块的自动更新
  3. `--lock` 标志会强制使用锁定文件
  4. `--lock-write` 标志会更新锁定文件

来源:cli/args/lockfile.rs30-48

错误处理

模块解析和加载错误会通过详细的错误消息进行处理

  1. “未找到模块”错误会显示导入路径和导入模块
  2. 语法错误会显示文件中的位置
  3. 类型错误会报告并附带源代码上下文
  4. 权限错误会指示所需的权限

来源:cli/graph_util.rs126-237 cli/module_loader.rs254-266

结论

Deno 的模块加载和解析系统通过结合基于浏览器的 ESM 加载和 Node.js 兼容性功能,提供了一种安全、高效的方式来处理各种来源的依赖项。Deno 创造了一种现代开发体验,同时在需要时保持向后兼容性。