ArcGIS JS地图初始化:AMD与ESM两种模块加载机制对比解析
2026/9/14 18:36:23 网站建设 项目流程

做 Web GIS 前端一年以上的人,应该都有过这种经历:明明只是想“在网页里放一张地图”,打开 ArcGIS API for JavaScript 的官方示例,却被requireimport两种写法反复横跳搞晕。这个让无数新手困惑的点,其实就是 ArcGIS JS 地图初始化里的 AMD 与 ESM 两套模块体系。这篇文章我就用地图初始化作为切入点,把 AMD、ESM 两种引入方式各自的加载机制、配置细节、常见报错全部过一遍,帮你在下一次新建项目时少走弯路。

1. 为什么 ArcGIS JS 4.x 的地图初始化要先分清 AMD 与 ESM

1.1 从 3.x 到 4.x:模块化从配角变成主线

用过 ArcGIS API for JavaScript 3.x 的前端应该记得,3.x 时代页面里引入一个<script src="https://js.arcgis.com/3.x/"></script>,然后直接用new esri.Map()new esri.layers.ArcGISTiledMapServiceLayer()这类全局对象就行。那时候整个 API 像一个巨大的命名空间,所有类都挂在esri下面,新手学习成本不算高,缺点是浏览器得一次性下载好几 MB 的 JS,而且全局变量满天飞。

4.x 重写之后,官方彻底转向模块化架构,不再维护那套全局对象。所有功能被拆成了esri/Mapesri/views/MapViewesri/layers/FeatureLayer这样的独立模块。问题来了:浏览器原生并不认识“esri 包名”这种模块路径,所以必须有一套模块加载机制来解释这种路径。ArcGIS JS 4.x 给出的答案不是一套,而是两套——AMD 与 ESM。

很多新手把这当成“新旧 API 的区别”,其实不对。不管是require还是import,底层用的 Map、MapView 完全是同一套类,只是“把模块从服务器拉回来并注入代码”的方式不一样。理解了这一点,后面所有配置都顺了。

1.2 双轨制的历史来源:Dojo 与现代前端标准的交接

AMD 全称是 Asynchronous Module Definition,异步模块定义。注意,这跟 CPU/GPU 厂商没有关系,你翻文档时看到“AMD”相关字眼,指的是模块规范,不是硬件平台。ArcGIS API for JavaScript 前几代底层深度绑定了 Dojo 工具包,Dojo 生态里的模块加载器走的正是 AMD 规范。所以 4.x 早期版本里,官方所有示例都长这样:

require(["esri/Map", "esri/views/MapView"], function (Map, MapView) { // ... });

后来前端工程化成了主流,ES Modules 作为语言标准被所有现代浏览器支持,官方在 4.18 左右开始正式发布基于原生 ES Modules 的入口,也就是 npm 上的@arcgis/core包。这套入口可以用import直接写,配合 Vite、Webpack、Rollup 等构建工具非常顺手。

所以现在官方文档同一页经常给出两套示例,不是版本混乱,是官方刻意保留了两条兼容路线:一条服务传统多页应用和纯 HTML 页面,一条服务现代前端工程化项目。地图初始化教程作为第一节,正好把这两套体系讲清楚。

1.3 什么场景该用哪一套

我的建议比较实际:

  • 如果你在做一个不经过构建工具的普通页面、企业内部系统后台、临时数据可视化页面,或者只是想快速验证一个想法,直接用 AMD,一个 HTML 文件十几行代码就能跑起来。
  • 如果你的项目是 Vue、React、Vite、Webpack 这类工程化前端,或者你希望在代码里用import做静态依赖管理、享受代码提示和类型检查,那就用 ESM,走@arcgis/core包。

不要一上来就追新。我一个朋友在一个纯 jQuery 老系统里强行上 Vite,只为了用 ESM 方式引入 ArcGIS JS,结果光改造构建链路就花了两周,最后还被运维抱怨产物体积变大。工具没有绝对好坏,匹配使用场景才重要。

2. 两套最小可运行代码:先把地图“点亮”

2.1 容器和样式:所有初始化代码的大前提

不管 AMD 还是 ESM,地图初始化都绕不开一个 DOM 容器。通常约定是一个div,给它一个id="map"。但这个容器有个隐藏要求:必须有实际高度。MapView 创建时会读取容器尺寸来计算渲染区域,如果容器或者它的父级高度是 0,地图渲染出来就是一片空白,而控制台往往不报错。

这是我见过最频繁的新手问题,没有之一。明明代码每一步都照着文档抄,页面就是白屏,最后发现 CSS 里漏了height: 100%。所以标准模板里我会把html, body, #map的高度全部铺满:

html, body, #map { height: 100%; margin: 0; padding: 0; }

如果你的页面布局不允许地图占满全屏,那就给#map一个固定的像素高度,比如height: 600px。总之,定位类和渲染类的事都不用操心,先把高度问题解决。

2.2 AMD 版最小代码

用 AMD 方式,核心就是在<head>里依次放置三样东西:主题 CSS、dojoConfig、API 入口脚本,然后在底部用require创建地图。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ArcGIS JS 地图初始化 - AMD 方式</title> <link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/light/main.css"> <style> html, body, #map { height: 100%; margin: 0; padding: 0; } </style> <script> var dojoConfig = { async: true }; </script> <script src="https://js.arcgis.com/4.30/"></script> </head> <body> <div id="map"></div> <script> require(["esri/Map", "esri/views/MapView"], function (Map, MapView) { var map = new Map({ basemap: "topo-vector" }); var view = new MapView({ container: "map", map: map, center: [116.397, 39.909], zoom: 10 }); }); </script> </body> </html>

这段代码如果一切正常,页面上会渲染出一张带有地形底图的矢量地图,中心点在北京附近,缩放级别 10。注意require的第一个参数是依赖数组,里面写模块路径;第二个参数是回调函数,模块加载完成后会把对应的类作为参数传进来,顺序一一对应。

2.3 ESM 版最小代码

ESM 版稍微现代一点。官方 CDN 也提供了 ESM 入口,我们需要用<script type="importmap">@arcgis/core这个包名映射到 CDN 地址上,然后就可以在原生 ES Module 里import了。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ArcGIS JS 地图初始化 - ESM 方式</title> <link rel="stylesheet" href="https://js.arcgis.com/4.30/@arcgis/core/assets/esri/themes/light/main.css"> <style> html, body, #map { height: 100%; margin: 0; padding: 0; } </style> <script type="importmap"> { "imports": { "@arcgis/core": "https://js.arcgis.com/4.30/@arcgis/core" } } </script> </head> <body> <div id="map"></div> <script type="module"> import Map from "@arcgis/core/Map.js"; import MapView from "@arcgis/core/views/MapView.js"; const map = new Map({ basemap: "topo-vector" }); const view = new MapView({ container: "map", map: map, center: [116.397, 39.909], zoom: 10 }); </script> </body> </html>

比较一下就会发现,除了加载方式和模块写法,核心代码几乎一模一样。真正需要new Map()new MapView()的逻辑完全复用,这正是双轨制设计的巧妙之处——API 是同一套,变的只是“怎么把这套 API 拿过来”。

2.4 两版代码的差异总结

我用表格整理一下便于你选型:

对比项AMD 方式ESM 方式
模块规范Dojo 加载器里的 require浏览器原生 ES Module
依赖管理依赖数组 + 回调import 静态导入
是否需要构建工具不需要,直接打开 HTML 就能跑纯原生需要 importmap,工程化走 npm 包
代码提示/类型检查基本没有配合 TS 较好
产物体积优化难,按需加载靠手动分包构建工具可以 tree-shaking(有上限)
适合项目传统页面、快速验证、内网系统Vue/React/Vite/Webpack 工程化项目

我个人建议:如果只是写 demo、做笔记、带新人入门,AMD 开一个 HTML 文件最快;如果做正式项目,直接选 ESM 配合构建工具,后面加图层、加 Widget、写业务逻辑都更舒服。

3. AMD 本地部署的配置细节:dojoConfig 与加载路径

3.1 CDN 和本地部署的脚本顺序

很多企业项目不能直接用外网 CDN,要把 ArcGIS JS API 放到自己服务器上,也就是本地部署。AMD 方式的本地部署有个非常容易踩坑的脚本顺序问题。

以官方下载包为例,解压后通常得到这样的目录结构:

arcgis_js_api/ library/ 4.30/ 4.30/ esri/ @arcgis/ init.js

在 HTML 里加载时,脚本顺序必须严格保持:先写dojoConfig,再写init.js。原因是init.js内部会读取dojoConfig来知道模块解析规则的路径,如果你把顺序写反,dojoConfig还没定义,加载器拿到一个 undefined 配置,后续所有模块路径都会对不上。

3.2 dojoConfig 到底在配什么

AMD 方式里,dojoConfig承担模块路径的解释工作。最简单的用法是:

var dojoConfig = { async: true };

async: true告诉加载器采用异步加载模式,这是 ArcGIS JS 4.x 推荐的做法。更关键的是packages字段,它可以把模块名esri映射到服务器上的真实目录。

本地部署时,官方安装文档里会要求你配置类似这样的内容:

var dojoConfig = { async: true, packages: [ { name: "esri", location: "/arcgis_js_api/library/4.30/4.30/esri" }, { name: "@arcgis/core", location: "/arcgis_js_api/library/4.30/4.30/@arcgis/core" } ] };

这里name是模块名,location是它在服务器上的实际路径。当你在代码里require(["esri/Map"])时,加载器就会拼接出/arcgis_js_api/library/4.30/4.30/esri/Map.js去请求。如果location配错,控制台里的报错几乎 100% 是 404。

3.3 本地 library 路径 rewrite

本地部署版本对路径更敏感,有一个细节我反复提醒身边同事:包名和版本目录之间存在双层4.30/4.30,这是官方拉取的压缩包结构和 CDN 路径不一致导致的。很多人第一次部署,把location写成/arcgis_js_api/library/4.30/esri,少了第二层,结果所有模块全部 404。

如果你使用 Nginx 托管,最简单的办法不是去改location,而是把请求路径直接重写到物理目录上。比如:

location /arcgis_js_api/ { alias /var/www/arcgis_js_api/library/4.30/4.30/; }

这样前端依然写/arcgis_js_api/4.30/init.js这类地址,但实际文件从正确目录读取。你要是问为什么官方压缩包的结构要这样设计,我只能说这是历史包袱,最省事的处理方式就是用一个 alias 固定住。

3.4 什么时候应当坚持 AMD

即使 ESM 已经这么成熟,我还是会建议一部分项目继续用 AMD。典型场景是:项目是纯前端静态页面,没有 Node 环境、没有包管理器、服务器不支持复杂构建流程,或者开发同事完全不熟悉前端工程化。

另外,ArcGIS JS 4.x 的 AMD 版本支持在require调用里动态加载模块,这对某些插拔式业务很有用。比如一个功能只有在点击按钮时才加载esri/widgets/Measurement,这种懒加载用 AMD 自带的加载机制实现起来很自然,不需要额外做代码分割。

4. ESM 工程化集成:从官方包到 Vite/Webpack

4.1 importmap 方式:适合原生的现代浏览器

如果你不想引入构建工具,只想在原生浏览器环境里用 ESM,官方文档给出的方案就是importmap。它本质上是一种浏览器原生支持的包名映射:

<script type="importmap"> { "imports": { "@arcgis/core": "https://js.arcgis.com/4.30/@arcgis/core" } } </script>

浏览器遇到import Map from "@arcgis/core/Map.js"时,会到映射地址去请求模块。这个方案的优点是零构建,缺点是浏览器兼容性有门槛,太老的环境不支持importmap。而且原生 importmap 在 HTTP 缓存优化上不如打包工具,模块文件数量多,开发时打开 DevTools 能看到一大堆请求。

4.2 npm 包方式:@arcgis/core的使用

正式工程化项目我更推荐直接用 npm 包:

npm install @arcgis/core@4.30

然后正常导入:

import Map from "@arcgis/core/Map.js"; import MapView from "@arcgis/core/views/MapView.js"; import esriConfig from "@arcgis/core/config.js";

注意导入路径末尾的.js,官方 ESM 包遵循 Node ESM 规范,不能省略扩展名。这是很多人第一次用会犯的错,在 Vite 里有时省略了也能跑,但换到严格 ESM 环境下就报模块找不到。

4.3 与构建工具结合的注意点

ESM 方式和构建工具结合时,有三个坑必须提前处理。

第一个坑是资源路径。ArcGIS JS 里的图片、字体、图标等静态资源全部默认相对assets目录加载。使用 npm 包后,assets目录在node_modules/@arcgis/core/assets下,构建工具不会自动把它复制到你的静态资源目录。你需要手动配置:

import esriConfig from "@arcgis/core/config.js"; // 在初始化地图之前 esriConfig.assetsPath = "/arcgis/assets";

然后把node_modules/@arcgis/core/assets整个目录复制到项目public/arcgis/assets下。不配这个,你会在控制台看到一堆字体、图标请求 404。

第二个坑是样式引入。AMD 方式里你只在 HTML 写了<link>引主题 CSS,ESM 方式在组件化开发时可以直接在 JS 里引入:

import "@arcgis/core/assets/esri/themes/light/main.css";

Vite 会帮你处理 CSS 依赖。这样地图相关的样式和组件样式走同一套构建流程,不会出现地图控件样式失效的问题。

第三个坑是版本一致性。项目里如果同时使用了 ArcGIS JS 的其他 SDK 包,必须保证@arcgis/core的版本和其他扩展包(比如@arcgis/map-components)版本一致。官方版本策略是 major.minor 完全对应,比如 4.30 的包最好只和同为 4.30 的扩展包混用,否则容易出现内部模块路径对不上。

4.4 tree-shaking 的实际收益

很多人从 AMD 转 ESM 的动机是“支持 tree-shaking,能减小体积”。实际效果要分两层看。

官方的@arcgis/core确实按模块拆了文件,import Map from "@arcgis/core/Map.js"不会把整个 2D/3D 引擎全部拉下来。但即便只初始化一个简单二维地图,底层渲染核心也有一大坨基础代码无法继续拆分,所以打包产物肯定比“只 import 一个 Lodash 函数那种比例”大很多。

我的实测结果是:一个只有地图初始化的 Vue 项目,Vite 构建后 gzip 体积大概在几百 KB 到 1 MB 之间,如果引入 3D 模块会明显上涨。相比 AMD 整个 CDN 脚本动辄几 MB 不分青红皂白全部加载,还是有优化空间,但别指望它能压缩到几十 KB 的轻量级。

5. 初始视野设置:center、zoom 与 basemap 的真实行为

5.1 center 的经纬度顺序,别搞反

MapView 初始化时,center参数接收一个经纬度数组,但顺序是[经度, 纬度],也就是[x, y]。这一点恰恰和很多人的直觉相反,我们平时说“北京,东经 116.4,北纬 39.9”,很容易写成[39.9, 116.4]

如果写反了,地图不会报错,而是定位到一个完全不相干的地方。我第一次写 ArcGIS JS 的时候就被这个坑过,明明中心点设为北京,页面加载出来却跑到了印度洋某片海域,排查了半天才发现是把经纬度顺序搞反了。

正确写法:

const view = new MapView({ container: "map", map: map, center: [116.397, 39.909], // 经度在前,纬度在后 zoom: 10 });

5.2 zoom 与 scale 的关系

zoom表示缩放级别,数值越大,视野越近。ArcGIS JS 4.x 默认底图的缩放级别范围一般是 0 到 23 左右。也有人喜欢用scale,它表示比例尺分母,scale: 2500000等价于某个缩放级别。两者设置一个即可,不能同时设置两个,如果同时写了,可能会以其中一个为主,但官方并不推荐这种写法。

实际业务里如果要求“首页展示整个区县”,最稳妥的做法是先通过一个经纬度中心和缩放级别大概卡一个范围,运行后微调。如果你需要精确知道某个缩放级别对应的比例尺,可以在开发工具里输出view.scale查看当前值,反向去标定。

5.3 basemap 传字符串还是自定义对象

初始化时basemap最常见的写法是传一个字符串 key:

basemap: "topo-vector"

可选值包括"streets-vector""satellite""hybrid""dark-gray-vector""osm"等。这些字符串是官方预置底图的别名,内部会解析成对应的底图图层集合。

如果你的系统内网无法访问公共底图服务,或者你有自建的切片服务,basemap需要传一个对象,手动指定底图图层:

const map = new Map({ basemap: { baseLayers: [ new TileLayer({ url: "https://你的服务器/arcgis/rest/services/你的切片/MapServer" }) ] } });

这里的核心区分在于:字符串底图方便,但依赖 ArcGIS 官方在线服务;对象底图灵活,适合离线内网项目。做政企项目时,我几乎都会直接改成对象底图,把底图服务指向客户自己的 GIS 服务,省去外网访问依赖。

5.4 其他初始化参数

MapView 还能接收rotationconstraintsui等参数。新手可以先不关注,但有一个建议:初始化时把popup的默认行为了解清楚,因为之后加图层的弹窗、标注全都会挂在这个视图上。地图初始化的目标是“先把视图跑通”,后面再一点点叠加。

6. 初始化阶段最常见的报错与排查记录

6.1 白屏:先看一眼容器高度

地图区域一片空白时,优先检查两件事:容器高度、API 是否成功加载。

容器高度的问题前面已经说过。API 加载失败可以通过 Network 面板看 JS 请求有没有 404,F12 打开控制台看有没有红色的语法错误或模块加载错误。还有一种情况是容器被 z-index 或者其他元素遮住了,地图已经渲染出来但看不到。这种情况可以在控制台执行view.container.clientHeight,如果返回值是 0,说明容器本身高度问题;如果返回值正常但仍然看不见,检查 CSS 定位和层级。

6.2 404:路径、baseUrl、packages 不一致

404 多半是模块路径问题。AMD 本地部署时,看dojoConfig.packages里的location是否正确;ESM 方式看importmap地址是否可达,本地服务有没有正确代理静态资源。

还有一个常见 404 来自主题 CSS。AMD 版 CSS 路径是有的开发者从网上抄来/esri/themes/dark/main.css,实际上主题目录里文件名可能是main.cssdark/main.csslight/main.css,必须与 CDN 结构一致。如果路径不对,浏览器只是加载不到样式,地图功能正常,但界面会变得非常简陋。

6.3 跨域错误与底图加载失败

在本地开发时,如果你使用的底图是其他域名的 ArcGIS Server 切片服务,浏览器会拦截跨域请求。控制台报错可能是 “CORS violation” 或者请求没有响应。解决的思路有几个:

  • 目标服务允许跨域,在 ArcGIS Server 管理界面勾选 CORS 支持;
  • 让后端配置反向代理,把外部服务地址代理到同域名下;
  • 如果完全不能改服务端,只能换一个支持 CORS 的底图源。

另外,使用 ArcGIS Online 公共底图时,部分高版本 API 要求访问服务带 token 或者 API key。如果控制台出现 “Token Required” 或者 403,多半是底图服务鉴权问题。这时候要么申请一个免费 API key 配到请求参数里,要么直接换成自建底图服务。

6.4 Vue/React 生命周期中的初始化与销毁

在组件化框架里做地图初始化,生命周期把控特别重要。不能在组件渲染到 DOM 之前就把 MapView 创建出来,因为那时候容器还不存在。

Vue 里我会这样写:

import { onMounted, onUnmounted, ref } from "vue"; import Map from "@arcgis/core/Map.js"; import MapView from "@arcgis/core/views/MapView.js"; const mapDiv = ref(null); let view = null; onMounted(() => { const map = new Map({ basemap: "topo-vector" }); view = new MapView({ container: mapDiv.value, map: map, center: [116.397, 39.909], zoom: 10 }); }); onUnmounted(() => { if (view) { view.destroy(); view = null; } });

destroy()非常重要。地图视图内部有大量事件监听、WebGL 渲染上下文和定时器,不销毁的话,组件卸载后内存依然被占着,刷新几次页面内存就上去了。React 的useEffectcleanup 同理。很多人只写初始化不写销毁,跑个 CRM 系统长期不刷新,页面越来越卡,跟这个有很大关系。

7. 从 AMD 迁到 ESM:实际项目迁移笔记

7.1 迁移前要盘点哪些东西

如果手头有一个老项目用的是 AMD 方式,想干净地迁到 ESM,第一步不是改代码,而是盘点现状。我一般按三张清单走:

  • 现有页面用了哪些模块:esri/Mapesri/views/MapViewesri/layers/*esri/widgets/*
  • 有没有自定义 AMD 插件或第三方扩展模块,它们的依赖是否只兼容 Dojo 加载器;
  • 页面里有没有使用全局dojodijit等 Dojo 组件的地方。

第三点最容易忽略。很多老系统不仅用了 ArcGIS JS,还直接用 Dojo 的dijit/form/Button来画 UI 控件,这些如果一起迁到 ESM,工作量大得惊人。若只是地图部分,则可以把 ArcGIS 相关模块换成@arcgis/core,UI 部分仍然留在原页面。

7.2 替换 require 的具体路线

对涉及到的地图代码,核心替换逻辑很机械:

// AMD 写法 require(["esri/Map", "esri/views/MapView"], function (Map, MapView) { ... });

改成:

// ESM 写法 import Map from "@arcgis/core/Map.js"; import MapView from "@arcgis/core/views/MapView.js";

如果原来是局部require按需加载,改成 ESM 后可以直接在文件顶部静态导入。但如果这个模块真的很大,而且只在某条业务分支里使用,就需要用import()动态导入来模拟原来的懒加载:

const { default: MeasureWidget } = await import("@arcgis/core/widgets/Measure.js");

注意 ESM 包导出的是命名导出和默认导出并存,官方模块通常是默认导出类,动态导入时需要用.default取到类。这个细节很容易被忽略,我在迁移时踩过。

7.3 最容易漏掉的三件事

第一件是样式。AMD 项目里样式是 HTML 里的<link>标签引的,迁移后依然可以保留,但如果是 Vue/React 组件里写的,最好改成:

import "@arcgis/core/assets/esri/themes/light/main.css";

不引样式的话,地图能显示,但弹窗、比例尺、缩放控件全部没有样式,丑到你怀疑 API 坏了。

第二件是assetsPath。前面提过,ESM 本地部署必须手动指定:

import esriConfig from "@arcgis/core/config.js"; esriConfig.assetsPath = "/arcgis/assets";

并确保assets目录被复制到静态资源服务下。漏掉它会看到地图上的符号、字体、WebGL shader 相关资源加载异常,提示很多,但都不直接指向配置项,比较难排查。

第三件是底图服务引用方式的调整。AMD 时代如果使用代码里写了完整的TileLayer地址,这个逻辑可以不变,但如果使用了官方默认底图字符串,迁移后仍会走 ArcGIS Online 公共底图,内网环境依旧白屏。借着迁移机会,把底图统一改成私有服务切片是更稳妥的做法。

7.4 迁移后的验证清单

迁移完成后别急着上线,先按这个清单过一遍:

  • 地图能不能在多个页面正常初始化;
  • 组件切换时有没有内存持续增长,控制台有没有 destroy 相关告警;
  • 弹窗、图例、缩放控件、比例尺等是否正常;
  • 网络请求里有没有大量重复加载同一模块;
  • 构建产物体积是否可接受,必要时对动态导入的路由做单独分包。

这套清单看起来基础,但能拦住 90% 的迁移回归问题。我自己迁移过一次包含二十多个地图页面的大型系统,最耗时的反而是那些页面里 AMD/Dojo 业务耦合比较深的地方,纯地图部分替换起来并不难。

地图初始化作为 ArcGIS JS 系列教程的第一篇,本身不复杂,难点主要在于两套模块体系的认知成本和工程配置的零散细节。把本文章的内容跑通一遍,你后续学图层加载、符号渲染、业务弹窗时,才能把精力放在 GIS 逻辑本身,而不是频繁被加载方式打断节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询