关于 arcgis-starter-app
arcgis-starter-app 为使用 TypeScript 和 Vite 构建 ArcGIS Maps SDK for JavaScript 应用的开发者提供指南,包含精简与面向生产的配置,以及 Map Components、Core API、React 和 Angular 示例。
主要功能
- 01精简与面向生产的项目配置
- 02TypeScript 和 Vite 设置
- 032D 地图与 3D 场景示例
- 04React 和 Angular 集成指南
- 05构建、代码检查与部署配置
适用场景
- 01启动小型 ArcGIS 制图原型
- 02搭建面向生产的 Web 制图项目
- 03在 React 或 Angular 中集成 ArcGIS Map Components
- 04排查 API 密钥与 TypeScript 模块解析问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ saschabrunnerch/ arcgis-maps-sdk-js-ai-context --skill arcgis-starter-app安装后在 Claude Code 中输入/arcgis-starter-app确认已加载。
附带文件AGENTS.md
详细介绍
ArcGIS Starter 应用
使用此技能,通过 TypeScript 和 Vite 创建 ArcGIS Maps SDK for JavaScript 应用。可选择用于快速原型开发的最简配置,或包含完整工具链的生产就绪配置。
前提条件
- ArcGIS 账户:需要 ArcGIS Location Platform、ArcGIS Online 或 ArcGIS Enterprise 账户,才能访问内容和服务。
- 访问令牌:通过门户创建具有适当权限(底图、地理编码、路径规划等)的访问令牌,并为生产环境配置允许的来源。
- Node.js:LTS 版本(20+),搭配 npm 或 pnpm。
注意: 使用 npm 时,Map Components 的 CSS 会自动加载,无需手动导入
@arcgis/map-components的 CSS。仅在直接使用 Core API 微件时才需要导入@arcgis/core的 CSS。
框架集成
React 19
Map Components 是 Web 组件(自定义元素)。在 React 19 中,可直接使用,并通过 on 前缀附加事件监听器:
import "@arcgis/map-components/dist/components/arcgis-map";
import "@arcgis/map-components/dist/components/arcgis-zoom";
function App() {
return (
<arcgis-map
basemap="topo-vector"
center="-118.24,34.05"
zoom={12}
onarcgisViewReadyChange={(e: CustomEvent) => {
const { view } = e.detail;
console.log("View ready:", view);
}}
>
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
);
}
重要: 请使用事件监听器(例如
onarcgisViewReadyChange),而不要直接在元素 ref 上调用方法,因为组件在渲染时可能尚未定义。
Angular
将 CUSTOM_ELEMENTS_SCHEMA 添加到模块或独立组件中,以便 Angular 识别非 Angular 元素:
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
@Component({
selector: "app-map",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<arcgis-map basemap="topo-vector" center="-118.24,34.05" [zoom]="12">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
`,
})
export class MapComponent {}
在根样式表中显式导入 Map Components CSS:
/* styles.css */
@import "@arcgis/map-components/dist/components/arcgis-map/arcgis-map.css";
TypeScript 配置
在 tsconfig.json 中将 moduleResolution 设为 "node16" 或 "bundler",以正确解析 @arcgis/map-components 包导出:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
最简配置
项目结构
my-arcgis-app/
├── src/
│ ├── main.ts
│ └── style.css
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── .gitignore
└── README.md
package.json
{
"name": "my-arcgis-app",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "~5.9.3",
"vite": "^7.2.7"
},
"dependencies": {
"@arcgis/map-components": "^5.0.0",
"@esri/calcite-components": "^3.3.3"
}
}
index.html(2D 地图)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>ArcGIS Map App</title>
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<calcite-shell content-behind>
<arcgis-map item-id="YOUR_WEBMAP_ID">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-legend slot="bottom-left"></arcgis-legend>
</arcgis-map>
</calcite-shell>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
index.html(3D 场景)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>ArcGIS Scene App</title>
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<calcite-shell content-behind>
<arcgis-scene item-id="YOUR_WEBSCENE_ID">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"></arcgis-compass>
</arcgis-scene>
</calcite-shell>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
src/main.ts(Map Components)
import "@arcgis/map-components/dist/components/arcgis-map";
import "@arcgis/map-components/dist/components/arcgis-zoom";
import "@arcgis/map-components/dist/components/arcgis-legend";
// For 3D scenes, also import:
// import "@arcgis/map-components/dist/components/arcgis-scene";
// import "@arcgis/map-components/dist/components/arcgis-navigation-toggle";
// import "@arcgis/map-components/dist/components/arcgis-compass";
import "@esri/calcite-components/dist/components/calcite-shell";
import { setAssetPath as setCalciteAssetPath } from "@esri/calcite-components/dist/components";
// Set Calcite assets path
setCalciteAssetPath("https://js.arcgis.com/calcite-components/3.3.3/assets");
// Configure ArcGIS API key
import esriConfig from "@arcgis/core/config";
esriConfig.apiKey = "YOUR_API_KEY";
// Wait for map to be ready
const arcgisMap = document.querySelector("arcgis-map");
arcgisMap?.addEventListener("arcgisViewReadyChange", (event) => {
const { view } = (event as CustomEvent).detail;
console.log("Map view ready:", view);
});
src/main.ts(Core API - 编程式)
import "@esri/calcite-components/dist/components/calcite-shell";
import { setAssetPath as setCalciteAssetPath } from "@esri/calcite-components/dist/components";
import Map from "@arcgis/core/Map.js";
import MapView from "@arcgis/core/views/MapView.js";
// Set Calcite assets path
setCalciteAssetPath("https://js.arcgis.com/calcite-components/3.3.3/assets");
const map = new Map({ basemap: "topo-vector" });
// Create view
const view = new MapView({
container: "viewDiv",
map: map,
center: [-118.805, 34.027],
zoom: 13,
});
src/style.css
@import "@arcgis/core/assets/esri/themes/light/main.css";
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
calcite-shell {
width: 100%;
height: 100%;
}
arcgis-map,
arcgis-scene,
#viewDiv {
width: 100%;
height: 100%;
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
build: { target: "esnext" },
});
.gitignore
node_modules/
dist/
.env
.env.local
快速开始
npm install @arcgis/map-components @esri/calcite-components
npm install -D typescript vite
npm run dev
常用微件
// Import additional components as needed
import "@arcgis/map-components/dist/components/arcgis-search";
import "@arcgis/map-components/dist/components/arcgis-basemap-gallery";
import "@arcgis/map-components/dist/components/arcgis-layer-list";
<arcgis-map item-id="YOUR_MAP_ID">
<arcgis-search slot="top-right"></arcgis-search>
<arcgis-basemap-gallery slot="top-right"></arcgis-basemap-gallery>
</arcgis-map>
生产环境配置
项目结构
my-arcgis-app/
├── .github/
│ └── workflows/
│ ├── test.yml
│ └── deploy.yml
├── src/
│ ├── main.ts
│ └── style.css
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── eslint.config.js
├── .prettierrc
├── .prettierignore
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .env.example
└── README.md
package.json
{
"name": "my-arcgis-app",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint .",
"format:check": "prettier --check .",
"format": "prettier --write .",
"prepare": "simple-git-hooks"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"eslint": "^9.39.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-unicorn": "^55.0.0",
"globals": "^16.5.0",
"prettier": "^3.7.4",
"lint-staged": "^16.2.7",
"simple-git-hooks": "^2.13.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.49.0",
"vite": "^7.2.7"
},
"dependencies": {
"@arcgis/core": "5.0.0",
"@arcgis/map-components": "^5.0.0",
"@esri/calcite-components": "^3.3.3"
},
"simple-git-hooks": {
"pre-commit": "pnpm exec lint-staged",
"pre-push": "pnpm exec tsc --noEmit"
},
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{js,json,css,md,yml,yaml}": ["prettier --write"]
}
}
eslint.config.js
import eslint from "@eslint/js";
import configPrettier from "eslint-config-prettier";
import importPlugin from "eslint-plugin-import";
import unicornPlugin from "eslint-plugin-unicorn";
import globals from "globals";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
// Enable type-aware linting for TypeScript files
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
projectService: true,
allowDefaultProject: ["vite.config.ts"],
},
},
},
// Scope type-checked recommendations strictly to TS files
...tseslint.configs.recommendedTypeChecked.map((cfg) => ({
...cfg,
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
})),
{
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser,
},
},
},
{
ignores: ["dist/**", "node_modules/**"],
},
{
ignores: ["vite.config.ts"],
},
{
plugins: {
import: importPlugin,
unicorn: unicornPlugin,
},
rules: {
// import hygiene
"import/no-duplicates": "error",
"import/no-useless-path-segments": "error",
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: [
"**/eslint.config.*",
"**/vite.config.*",
"**/*.config.*",
"**/scripts/**",
"**/*.test.*",
"**/*.spec.*",
".github/**",
],
optionalDependencies: false,
peerDependencies: true,
},
],
"import/order": [
"warn",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
},
],
// unused vars/imports
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
// code quality
eqeqeq: ["error", "always"],
"no-console": ["warn", { allow: ["warn", "error"] }],
"@typescript-eslint/no-explicit-any": "warn",
// unicorn essentials
"unicorn/prefer-node-protocol": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-array-find": "error",
"unicorn/throw-new-error": "error",
// typescript consistency
"@typescript-eslint/consistent-type-imports": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-require-imports": "error",
},
},
// Node environment for config and build scripts
{
files: ["eslint.config.js", "vite.config.*", "*.config.*", "scripts/**"],
languageOptions: {
globals: {
...globals.node,
},
},
},
// Declarations: relax strictness for ambient types
{
files: ["**/*.d.ts"],
rules: {
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
},
},
// TS-only: enable balanced type-aware rules
{
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
rules: {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": [
"error",
{ checksVoidReturn: false },
],
"@typescript-eslint/await-thenable": "warn",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
"@typescript-eslint/no-redundant-type-constituents": "off",
},
},
// Disable ESLint rules that would conflict with Prettier formatting
configPrettier,
);
.prettierrc
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"endOfLine": "lf",
"jsxSingleQuote": false,
"printWidth": 120,
"quoteProps": "consistent",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css"
}
.prettierignore
dist/
node_modules/
*.min.js
package-lock.json
pnpm-lock.yaml
.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
.gitattributes
* text=auto eol=lf
.gitignore
node_modules/
dist/
.env
.env.local
.env.*.local
*.log
.DS_Store
*.tsbuildinfo
.env.example
# ArcGIS API Key
# Get your API key from https://developers.arcgis.com/
VITE_ARCGIS_API_KEY=your_arcgis_api_key_here
.github/workflows/test.yml
name: Test
on:
pull_request:
branches:
- main
permissions:
contents: read
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Security audit
run: pnpm audit --audit-level=high
- name: ESLint
run: pnpm run lint
- name: Prettier check
run: pnpm run format:check
- name: Annotate formatting issues
if: failure()
uses: actions/github-script@v7
with:
script: |
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
'Prettier check failed. Please run `pnpm run format` locally to fix formatting.',
'',
`Workflow run: ${runUrl}`,
].join('\n');
if (context.payload.pull_request) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body,
});
} else {
core.summary.addHeading('Prettier check failed');
core.summary.addRaw(body);
await core.summary.write();
}
- name: Type check
run: pnpm exec tsc --noEmit
- name: Build
run: pnpm run build
env:
NODE_OPTIONS: "--max-old-space-size=4096"
VITE_ARCGIS_API_KEY: ${{ secrets.VITE_ARCGIS_API_KEY }}
.github/workflows/deploy.yml
name: Build & Deploy to GitHub Pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
env:
NODE_OPTIONS: "--max-old-space-size=4096"
VITE_ARCGIS_API_KEY: ${{ secrets.VITE_ARCGIS_API_KEY }}
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./dist"
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
生产环境 src/main.ts
import "@arcgis/map-components/dist/components/arcgis-scene";
import "@arcgis/map-components/dist/components/arcgis-zoom";
import "@arcgis/map-components/dist/components/arcgis-navigation-toggle";
import "@arcgis/map-components/dist/components/arcgis-compass";
import { setAssetPath as setCalciteAssetPath } from "@esri/calcite-components/dist/components";
import esriConfig from "@arcgis/core/config";
// Set Calcite assets path
setCalciteAssetPath("https://js.arcgis.com/calcite-components/3.3.3/assets");
// Configure ArcGIS API key from environment variable
esriConfig.apiKey = import.meta.env.VITE_ARCGIS_API_KEY as string;
// Wait for map to be ready
const arcgisScene = document.querySelector("arcgis-scene");
arcgisScene?.addEventListener("arcgisViewReadyChange", (event) => {
const { view } = (event as CustomEvent).detail;
console.warn("Scene view ready:", view);
});
快速开始(生产环境)
pnpm create vite my-arcgis-app --template vanilla-ts
cd my-arcgis-app
pnpm add @arcgis/map-components @arcgis/core @esri/calcite-components
pnpm add -D eslint @eslint/js typescript-eslint eslint-config-prettier eslint-plugin-import eslint-plugin-unicorn globals prettier simple-git-hooks lint-staged
pnpm run prepare
pnpm dev
相关技能
- 查看
arcgis-core-maps,了解地图和视图配置。 - 查看
arcgis-layers,了解如何向地图添加数据图层。 - 查看
arcgis-widgets-ui,了解如何添加微件和 UI 组件。
参考示例
intro-mapview- 创建基础 2D 地图的分步指南intro-map-components- Web 组件入门intro-sceneview- 为应用创建 3D 场景intro-graphics- 向地图添加图形和绘制功能intro-popup- 添加弹出窗口以显示要素信息
常见问题
-
未配置 API 密钥:应用引用了不存在或为空的环境变量。
// Anti-pattern: referencing env var without creating .env file import esriConfig from "@arcgis/core/config"; esriConfig.apiKey = import.meta.env.VITE_ARCGIS_API_KEY as string; // .env file is missing or VITE_ARCGIS_API_KEY is empty// Correct: create .env file with a valid API key // .env // VITE_ARCGIS_API_KEY=AAPTxy...your_actual_key_here import esriConfig from "@arcgis/core/config"; esriConfig.apiKey = import.meta.env.VITE_ARCGIS_API_KEY as string;**影响:**没有有效的 API 密钥,底图图块和位置服务(地理编码、路径规划)将无法加载。地图会显示为灰色或空白画布,且界面中不会显示任何错误。
-
tsconfig 中的 moduleResolution 设置错误:使用
"node"而不是"node16"或"bundler",会导致 TypeScript 无法解析包导出。// Anti-pattern: moduleResolution "node" cannot resolve package exports { "compilerOptions": { "moduleResolution": "node" } }// Correct: use "node16" or "bundler" for package exports support { "compilerOptions": { "moduleResolution": "bundler" } }**影响:**TypeScript 无法解析
@arcgis/map-components的子路径导出(例如@arcgis/map-components/dist/components/arcgis-map)。构建时会出现“Cannot find module”错误。 -
未配置 Calcite 资源:Calcite 图标和组件要求在运行时设置资源路径。
// Anti-pattern: importing Calcite components without setting asset path import "@esri/calcite-components/dist/components/calcite-shell"; // No setAssetPath call - icons and internal assets fail to load// Correct: set the Calcite asset path before using components import "@esri/calcite-components/dist/components/calcite-shell"; import { setAssetPath as setCalciteAssetPath } from "@esri/calcite-components/dist/components"; setCalciteAssetPath("https://js.arcgis.com/calcite-components/3.3.3/assets");**影响:**Calcite 图标会显示为空白方框,操作按钮不会显示图标,部分组件(例如
calcite-shell)也可能无法正确布局,因为它们无法获取内部资源。