ArcGIS JS API 5.0 ESM 双模块系统冲突解决方案
- [ArcGIS JS API 5.0 ESM 双模块系统冲突解决方案](#ArcGIS JS API 5.0 ESM 双模块系统冲突解决方案)
-
- 一、问题描述
- 二、问题原因
-
- [2.1 核心原因:AMD 与 ESM 模块系统混用](#2.1 核心原因:AMD 与 ESM 模块系统混用)
- [2.2 具体场景还原](#2.2 具体场景还原)
- [2.3 为什么 SpatialReference.WGS84 也会出问题](#2.3 为什么 SpatialReference.WGS84 也会出问题)
- 三、解决过程
-
- [第 1 步:尝试修复空间参考实例](#第 1 步:尝试修复空间参考实例)
- [第 2 步:添加 try-catch 回退](#第 2 步:添加 try-catch 回退)
- [第 3 步:根治方案------统一为纯 ESM 系统](#第 3 步:根治方案——统一为纯 ESM 系统)
- 四、解决方案
-
- [4.1 核心操作](#4.1 核心操作)
-
- [移除 AMD 入口](#移除 AMD 入口)
- [添加 Import Map](#添加 Import Map)
- [4.2 修改资源引入方式](#4.2 修改资源引入方式)
- [4.3 Import Map 的关键作用](#4.3 Import Map 的关键作用)
- [4.4 完整对比](#4.4 完整对比)

ArcGIS JS API 5.0 ESM 双模块系统冲突解决方案
一、问题描述
在将 ArcGIS JS API 5.0 的测试页面从 AMD($arcgis.import)迁移到 ESM(import ... from '...')引入方式时,页面运行时抛出以下类型检查错误:
[@arcgis/core/core/accessorSupport/ensureTypes]
Accessor#set Assigning an instance of 'arcgis.geometry.SpatialReference'
which is not a subclass of 'arcgis.geometry.SpatialReference'
进一步的链式错误还包括:
OperatorProject.js:3 Uncaught (in promise) TypeError:
Cannot read properties of null (reading 'hasVCS')
完整错误栈:
at OperatorProject.js:3:552630 at Kr (OperatorProject.js:3:553489) at Object.Qr [as create$3] (OperatorProject.js:3:552446) at e.createProjectionTransformation (projectionTransformation.js:3:924) at o.execute (projectOperator.js:3:231)
这些错误的直接表现是:空间参考坐标系对象类型不匹配,投影引擎无法正常工作。
二、问题原因
2.1 核心原因:AMD 与 ESM 模块系统混用
问题的根源在于同一个页面同时存在两套模块加载系统 ,它们各自维护独立的模块缓存,导致同一个逻辑类(如 SpatialReference)产生了两个不同的实例:
┌─────────────────────────────────────────────────────┐
│ 浏览器页面 │
│ │
│ ┌───────────────┐ ┌──────────────────────┐ │
│ │ core.js │ │ ESM 静态 import │ │
│ │ (AMD 系统) │ │ │ │
│ │ │ │ import Map from │ │
│ │ $arcgis │ │ './5.0/ │ │
│ │ .import() │ │ @arcgis/core/ │ │
│ │ │ │ Map.js' │ │
│ └───┬────────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ AMD 缓存 │ │ ESM 模块缓存 │ │
│ │ │ │ (浏览器原生) │ │
│ │ SpatialRef │ │ SpatialRef ←不同实例 │ │
│ │ erence (v1) │ │ erence (v2) │ │
│ └──────────────┘ └──────────────────────┘ │
│ │
│ v1 !== v2 → instanceof 检查失败 ❌ │
└─────────────────────────────────────────────────────┘
2.2 具体场景还原
测试页面 contour_demo.html 中的代码结构:
html
<!-- 1. 加载 core.js,引入 AMD 模块系统 -->
<script type="module" src="./5.0/core.js"></script>
<!-- 2. HTML 内使用 ESM 静态 import -->
<script type="module">
import SpatialReference from './5.0/@arcgis/core/geometry/SpatialReference.js';
// ...
</script>
与此同时,工具类 ContourAnalysis.js 内部:
javascript
async init() {
if (typeof $arcgis !== 'undefined' && $arcgis.import) {
// ── AMD 路径(因为 core.js 加载了,$arcgis 存在)──
[SpatialReference] = await $arcgis.import([
'@arcgis/core/geometry/SpatialReference.js'
]);
} else {
// ── ESM 路径(永远不会执行)──
const mods = await import('@arcgis/core/geometry/SpatialReference.js');
SpatialReference = mods.default;
}
this._spatialReference = new SpatialReference({ wkid: 4326 });
}
冲突链路:
core.js加载 → 全局$arcgis可用 →ContourAnalysis.init()走 AMD 路径- AMD 路径获取的
SpatialReference类 → 存储在 AMD 模块缓存中 - 页面
<script type="module">中import SpatialReference from ...→ 浏览器 ESM 模块缓存中 - 两个
SpatialReference是不同的类实例 - 框架内部
Accessor#set类型检查通过instanceof判断 → 检查失败
2.3 为什么 SpatialReference.WGS84 也会出问题
SpatialReference.WGS84 是一个静态 getter,返回缓存的空间参考实例。由于该类本身来自 AMD 系统,其内部缓存的 WGS84 实例携带的 VCS(垂直坐标系)属性可能为 null,导致投影引擎在调用 createProjectionTransformation() 时读取 hasVCS 失败。
三、解决过程
第 1 步:尝试修复空间参考实例
思路 :不依赖缓存单例 SpatialReference.WGS84,改为新建实例。
SpatialReference.WGS84 → new SpatialReference({ wkid: 4326 })
sr.isWGS84 → sr.wkid !== 4326
结果 :hasVCS 错误依旧。说明问题不在空间参考对象本身,而在投影引擎的模块系统一致性。
第 2 步:添加 try-catch 回退
思路 :在投影调用外层加 try-catch,异常时手动用 Web Mercator 数学公式转换坐标。
javascript
try {
geom = po.execute(geometry, this._spatialReference);
} catch (e) {
// Web Mercator → WGS84 手动转换
const R = 20037508.34;
geom = { rings: geometry.rings.map(r => r.map(p => {
return [p[0] / R * 180, 180 / Math.PI * (2 * Math.atan(Math.exp(p[1] / R * Math.PI)) - Math.PI / 2), p[2]];
})) };
}
结果 :绕过了投影引擎的 hasVCS 错误,但紧接着触发了 instanceof 类型检查错误。验证了根因是双模块系统冲突。
第 3 步:根治方案------统一为纯 ESM 系统
思路 :移除 core.js(AMD 入口),使 $arcgis 不可用,强制所有模块走 ESM 路径。
结果:所有错误消失,功能正常运行。手动回退代码也无需触发(纯 ESM 下投影引擎本身工作正常)。
四、解决方案
4.1 核心操作
移除 AMD 入口
diff
- <script type="module" src="../../5.0/core.js"></script>
添加 Import Map
html
<script type="importmap">
{
"imports": {
"@arcgis/core/": "../../5.0/@arcgis/core/"
}
}
</script>
4.2 修改资源引入方式
将 $arcgis.import() 调用替换为 ESM 静态 import 语句。
AMD 方式(修改前):
javascript
const [Map, SceneView, GraphicsLayer] = await $arcgis.import([
"@arcgis/core/Map.js",
"@arcgis/core/views/SceneView.js",
"@arcgis/core/layers/GraphicsLayer.js",
]);
ESM 方式(修改后):
由于 import map 已将
@arcgis/core/映射为../../5.0/@arcgis/core/,直接使用裸路径即可:
javascript
import Map from '@arcgis/core/Map.js';
import SceneView from '@arcgis/core/views/SceneView.js';
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer.js';
4.3 Import Map 的关键作用
| 方面 | 说明 |
|---|---|
| 裸路径解析 | 工具类内部 import('@arcgis/core/...') 的动态导入依赖 import map 来解析裸模块标识符 |
| 模块一致性 | 静态 import 和动态 import() 通过 import map 解析到同一 URL → 浏览器 ESM 缓存返回同一实例 |
| 部署灵活性 | 无需修改工具类源码中的 @arcgis/core/ 前缀,只需调整 HTML 中的 import map |
4.4 完整对比
| 修改前 | 修改后 | |
|---|---|---|
| AMD 入口 | <script type="module" src="core.js"> |
移除 |
| 导入方式 | await $arcgis.import([...]) |
import X from '@arcgis/core/X.js' |
| 裸路径解析 | 依赖 core.js 的 AMD 加载器 | Import Map |
| 模块系统 | AMD + ESM 混用 ❌ | 纯 ESM ✅ |
| 模块实例 | 两套缓存,类不相等 | 统一缓存,类唯一 |
| 投影引擎 | hasVCS: null 崩溃 |
正常工作 |