尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ArcGIS JS地图初始化:AMD与ESM两种模块加载机制对比解析

ArcGIS JS地图初始化:AMD与ESM两种模块加载机制对比解析 做 Web GIS 前端一年以上的人应该都有过这种经历明明只是想“在网页里放一张地图”打开 ArcGIS API for JavaScript 的官方示例却被require和import两种写法反复横跳搞晕。这个让无数新手困惑的点其实就是 ArcGIS JS 地图初始化里的 AMD 与 ESM 两套模块体系。这篇文章我就用地图初始化作为切入点把 AMD、ESM 两种引入方式各自的加载机制、配置细节、常见报错全部过一遍帮你在下一次新建项目时少走弯路。1. 为什么 ArcGIS JS 4.x 的地图初始化要先分清 AMD 与 ESM1.1 从 3.x 到 4.x模块化从配角变成主线用过 ArcGIS API for JavaScript 3.x 的前端应该记得3.x 时代页面里引入一个script srchttps://js.arcgis.com/3.x//script然后直接用new esri.Map()、new esri.layers.ArcGISTiledMapServiceLayer()这类全局对象就行。那时候整个 API 像一个巨大的命名空间所有类都挂在esri下面新手学习成本不算高缺点是浏览器得一次性下载好几 MB 的 JS而且全局变量满天飞。4.x 重写之后官方彻底转向模块化架构不再维护那套全局对象。所有功能被拆成了esri/Map、esri/views/MapView、esri/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给它一个idmap。但这个容器有个隐藏要求必须有实际高度。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 langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleArcGIS JS 地图初始化 - AMD 方式/title link relstylesheet hrefhttps://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 srchttps://js.arcgis.com/4.30//script /head body div idmap/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 typeimportmap把arcgis/core这个包名映射到 CDN 地址上然后就可以在原生 ES Module 里import了。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleArcGIS JS 地图初始化 - ESM 方式/title link relstylesheet hrefhttps://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 typeimportmap { imports: { arcgis/core: https://js.arcgis.com/4.30/arcgis/core } } /script /head body div idmap/div script typemodule 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/Webpack4.1 importmap 方式适合原生的现代浏览器如果你不想引入构建工具只想在原生浏览器环境里用 ESM官方文档给出的方案就是importmap。它本质上是一种浏览器原生支持的包名映射script typeimportmap { 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/core4.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引主题 CSSESM 方式在组件化开发时可以直接在 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最常见的写法是传一个字符串 keybasemap: 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 还能接收rotation、constraints、ui等参数。新手可以先不关注但有一个建议初始化时把popup的默认行为了解清楚因为之后加图层的弹窗、标注全都会挂在这个视图上。地图初始化的目标是“先把视图跑通”后面再一点点叠加。6. 初始化阶段最常见的报错与排查记录6.1 白屏先看一眼容器高度地图区域一片空白时优先检查两件事容器高度、API 是否成功加载。容器高度的问题前面已经说过。API 加载失败可以通过 Network 面板看 JS 请求有没有 404F12 打开控制台看有没有红色的语法错误或模块加载错误。还有一种情况是容器被 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.css、dark/main.css或light/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/Map、esri/views/MapView、esri/layers/*、esri/widgets/*有没有自定义 AMD 插件或第三方扩展模块它们的依赖是否只兼容 Dojo 加载器页面里有没有使用全局dojo、dijit等 Dojo 组件的地方。第三点最容易忽略。很多老系统不仅用了 ArcGIS JS还直接用 Dojo 的dijit/form/Button来画 UI 控件这些如果一起迁到 ESM工作量大得惊人。若只是地图部分则可以把 ArcGIS 相关模块换成arcgis/coreUI 部分仍然留在原页面。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 逻辑本身而不是频繁被加载方式打断节奏。
返回列表