├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── archived-examples.md
├── karma.conf.js
├── package.json
├── profiles
├── base.config.js
└── prod.config.js
├── rollup.config.js
├── scripts
└── release.sh
├── src
├── esri-loader.ts
├── modules.test.ts
├── modules.ts
├── script.test.ts
├── script.ts
└── utils
│ ├── css.test.ts
│ ├── css.ts
│ ├── index.ts
│ ├── url.test.ts
│ └── url.ts
├── test
├── helpers.ts
└── mocks
│ ├── jsapi3x.js
│ └── jsapi4x.js
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing).
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Expected behavior
2 |
3 | - Describe what you expected or wanted to happen.
4 | - What you are trying to achieve?
5 | - Describe your environment/framework and be specific with version numbers (e.g. React 16.2, react-router 4.2, redux 3.7, node 8.3).
6 |
7 | ### Actual behavior
8 |
9 | - Describe what occurs in your code.
10 | - Specifically, what seems to work differently than you intended?
11 | - Provide any error messages you see in the console.
12 |
13 | ### Steps to reproduce the behavior
14 |
15 | **We can only help you if we're able to easily reproduce the behavior you describe above.**
16 |
17 | Please provide:
18 |
19 | 1. Steps to reproduce the behavior.
20 | 2. A link to an app where we can carry out those steps and see the source code.
21 |
22 | To help you reproduce issues in an environment that we can't access (e.g. private repository) follow these helpful tips:
23 |
24 | - If the problem is related to the ArcGIS Maps SDK for JavaScript (i.e. behavior of the map, etc), start here https://github.com/Esri/esri-loader#without-a-module-bundler.
25 | - Otherwise, search for and fork a codesandbox that is similar to your environment (React, etc): https://codesandbox.io/search
26 | - For sandboxes that depend on esri-loader see: https://codesandbox.io/search?refinementList[npm_dependencies.dependency][0]=esri-loader
27 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | - Describe the proposed changes:
2 |
3 | - Is there an example or test page to demonstrate any new or changed features?
4 | - Does your PR include appropriate tests for source code alterations?
5 | - If you're adding or changing a public API, did you update the docs Usage sections of the README? If not, please provide a code snippet that demonstrates how to consume the new or updated API(s).
6 |
7 | - Provide a reference to any related issue.
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # install dependencies, run build and tests
2 |
3 | name: Node.js CI
4 |
5 | on:
6 | push:
7 | branches: [ master ]
8 | pull_request:
9 | branches: [ master ]
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | node-version: [14.x, 16.x]
22 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Use Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v3
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 | cache: 'yarn'
31 | - name: Install dependencies
32 | run: yarn
33 | - name: Install Firefox
34 | uses: browser-actions/setup-firefox@latest
35 | - name: Run tests
36 | run: env FIREFOX_BIN=`which firefox` yarn ci
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 |
30 | # Dependency directories
31 | node_modules
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
40 | # build output
41 | dist
42 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # esri-loader Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## [Unreleased]
6 | ### Added
7 | ### Changed
8 | ### Fixed
9 | ### Removed
10 | ### Breaking
11 |
12 | ## [3.7.0] - 2022-11-16
13 | ### Added
14 | - default to 4.25; update docs w/ latest version numbers - @andygup
15 | ### Changed
16 | - raise visibility of deprecation notice for frameworks that do not support async/await at runtime, e.g. Angular due to limitations in Zone.js
17 | - archive framework samples and various 3.x-related sections to archived-examples.md. Most of the samples haven't been updated in years
18 |
19 | ## [3.6.0] - 2022-07-07
20 | ### Added
21 | - default to 4.24; update docs w/ latest version numbers - @gavinr
22 |
23 | ### Changed
24 | - fix build by not compiling @types
25 | - update TypeScript and karma-typescript dependencies
26 | - use GitHub Actions instead of travis.yml
27 |
28 | ## [3.5.0] - 2022-03-29
29 | ### Added
30 | - default to 4.23; update docs w/ latest version numbers - @gavinr
31 |
32 | ## [3.4.0] - 2022-01-14
33 | ### Added
34 | - default to 4.22; update docs w/ latest version numbers - @gavinr
35 |
36 | ## [3.3.0] - 2021-09-22
37 |
38 | ## [3.2.0] - 2021-07-07
39 | ### Added
40 | - default to 4.20; update docs w/ latest version numbers - @gavinr
41 |
42 | ## [3.1.0] - 2021-04-23
43 | ### Added
44 | - default to 4.19; update docs w/ latest version numbers - @vannizhang
45 |
46 | ## [3.0.0] - 2020-12-31
47 | ### Added
48 | - default to 4.18; update docs w/ latest version numbers - @gavinr
49 |
50 | ### Breaking
51 | - 4.18 no longer supports IE
52 | - remove option to set `dojoConfig`
53 | - remove esri-loader default export
54 |
55 | ## [2.16.0] - 2020-10-13
56 | ### Added
57 | - default to 4.17; update docs w/ latest version numbers - @tgirgin23
58 |
59 | ## [2.15.0] - 2020-07-10
60 | ### Added
61 | - default to 4.16; update docs w/ latest version numbers - @JoshCrozier
62 |
63 | ## [2.14.0] - 2020-04-09
64 | ### Added
65 | - default to 4.15; update docs w/ latest version numbers - @JoshCrozier
66 |
67 | ## [2.13.0] - 2019-12-22
68 |
69 | ### Added
70 | - default to 4.14; update docs w/ latest version numbers - @gpbmike
71 |
72 | ## [2.12.0] - 2019-10-24
73 |
74 | ### Added
75 | - `setDefaultOptions()` to set default `loadScript()` options at app startup (#192) - thanks @JoshCrozier!
76 | ### Changed
77 | - updated README to emphasize `setDefaultOptions()` and link to the new [Framework Guides](https://developers.arcgis.com/javascript/latest/guide/using-frameworks/)
78 |
79 | ## [2.11.0] - 2019-10-14
80 | ### Added
81 | - default to 4.13; update docs w/ latest version numbers
82 | - add support for "next" version of
83 |
84 | ## [2.10.2] - 2019-10-12
85 |
86 | ### Changed
87 | - added "Using Modules Synchronously" to the docs (README) - thanks [@stdavis](https://github.com/stdavis)!
88 | ### Fixed
89 | - `css: true` uses the correct URL the light theme (`/esri/themes/light/main.css`) - thanks [@stdavis](https://github.com/stdavis)
90 |
91 | ## [2.10.1] - 2019-09-27
92 |
93 | ### Changed
94 | - Added generics for `loadModules` typings improvements. #183 - thanks [@deskoh](https://github.com/deskoh)!
95 |
96 | ## [2.10.0] - 2019-07-03
97 | ### Added
98 | - default to 4.12; update docs w/ latest version numbers
99 |
100 | ## [2.9.2] - 2019-04-18
101 |
102 | ### Fixed
103 | - window undefined in Node environments
104 |
105 | ## [2.9.1] - 2019-03-31
106 |
107 | ### Fixed
108 | - export missing ILoadScriptOptions
109 |
110 | ## [2.9.0] - 2019-03-29
111 | ### Added
112 | - default to 4.11; update docs w/ latest version numbers
113 |
114 | ## [2.8.0] - 2019-03-27
115 | ### Added
116 | - `loadScript()` takes a new `version` option to load a specific version from the CDN
117 | - passing `css: true` to `loadScript()` will load the styles for the CDN version
118 | - `loadCss()` defaults to loading the latest 4.x styles if no arguments are passed
119 | - `loadCss()` can take a version as a string to load a version's styles from the CDN
120 | ### Changed
121 | - split source code into modules
122 | - tests are now written in TypeScript and loaded via karma-typescript
123 | - updated to recent versions of TypeScript, Karma, & Jasmine
124 |
125 | ## [2.7.0] - 2019-03-26
126 | ### Added
127 | - `insertCssBefore` option to insert CSS link before an existing element
128 |
129 | ## [2.6.0] - 2018-12-17
130 |
131 | ### Added
132 | - default to 4.10; update docs w/ latest version numbers
133 |
134 | ## [2.5.0] - 2018-09-29
135 |
136 | ### Changed
137 | - default to 4.9; update docs w/ latest version numbers
138 |
139 | ## [2.4.0]
140 |
141 | ### Changed
142 | - default to 4.8; update docs w/ latest version numbers
143 |
144 | ## [2.3.0]
145 | ### Added
146 | - default to 4.7; update docs w/ latest version numbers
147 | ### Changed
148 | - added Hyperapp example link to README
149 | - move CSS functions into own module
150 | - no longer publishing src folder b/c it is not needed
151 |
152 | ## [2.2.0] - 2018-02-07
153 | ### Added
154 | - `loadScript()` takes a `css` option to load stylesheet by URL
155 | ### Changed
156 | - added Choo and Riot example links to README
157 |
158 | ## [2.1.0] - 2018-02-06
159 | ### Added
160 | - added loadCss(url) to inject a stylesheet link
161 | ### Changed
162 | - added GitHub issue and pull request templates
163 | - added badges to README
164 | - added section on updating from previous versions to README
165 | - added reusable library sections for Angular and React
166 | - added links to ember-esri-loader examples and CanJS
167 | - changed npm scripts to rely on rimraf and mkdirp for Windows support
168 | - check in yarn.lockfile and use yarn
169 |
170 | ## [2.0.0] - 2018-01-04
171 |
172 | ### Changed
173 | - misc README updates
174 |
175 | ### Breaking
176 | - remove deprecated bootstrap() and dojoRequire() functions
177 | - isLoaded() no longer checks if the script exists
178 | - no longer distribute builds at dist root
179 |
180 | ## [1.7.0] - 2018-01-03
181 | ### Added
182 | - make getScript() a public API [#44](https://github.com/Esri/esri-loader/issues/44)
183 | ### Changed
184 | - Add link to esri-vue-cli-example to README
185 |
186 | ## [1.6.2] - 2018-01-02
187 |
188 | ### Fixed
189 | - fallback to current url when loading modules [#51](https://github.com/Esri/esri-loader/issues/51) [#61](https://github.com/Esri/esri-loader/issues/61)
190 |
191 | ## [1.6.1] - 2018-01-01
192 |
193 | ### Changed
194 | - added Advanced Usage section and info on isomorphic apps to README
195 | ### Fixed
196 | - `script.dataset` is `undefined` in IE10 [#67](https://github.com/Esri/esri-loader/pull/67)
197 |
198 | ## [1.6.0] - 2017-12-31
199 | ### Added
200 | - default to version 4.6 [#63](https://github.com/Esri/esri-loader/issues/63)
201 | ### Changed
202 | - remove remaining references to angular-esri-loader from README
203 | - update README w/ info on arcgis types and browser support [#60](https://github.com/Esri/esri-loader/issues/60)
204 | ### Fixed
205 | - window undefined error in server-rendered apps [#64](https://github.com/Esri/esri-loader/issues/64)
206 |
207 | ## [1.5.3] - 2017-11-20
208 |
209 | ### Changed
210 | - use rollup's uglify plugin for minified umd build
211 | - don't generate sourcemaps when compiling TypeScript
212 | ### Fixed
213 | - re-include umd at dist root to avoid breaking apps w/ hardcoded path
214 |
215 | ## [1.5.2] - 2017-11-18
216 |
217 | ### Fixed
218 | - uglify sourcemap url uses relative path
219 |
220 | ## [1.5.1] - 2017-11-17
221 |
222 | ### Changed
223 | - output esm and .d.ts to dist folder, only include dist/src when publishing
224 |
225 | ## [1.5.0] - 2017-11-09
226 | ### Added
227 | - add promise-based functions to load the script and modules
228 | ### Changed
229 | - deprecate `bootstrap()` and `dojoRequire()`
230 | - add code coverage
231 | - add release script
232 |
233 | ## [1.4.0] - 2017-11-07
234 |
235 | ### Added
236 | - handle script load errors
237 |
238 | ## [1.3.0]
239 |
240 | ### Added
241 | - set `window.dojoConfig` by passing as an option to `bootstrap()`
242 |
243 | ### Changed
244 | - no longer running tests in phantom
245 |
246 | ## [1.2.1]
247 |
248 | ### Fixed
249 | - defintion of `dojoRequire()`'s callback
250 |
251 | ## [1.2.0]
252 |
253 | ### Added
254 | - default to 4.5
255 |
256 | ### Fixed
257 | - don't throw an error when `bootstrap()` is called multiple times w/o a callback
258 |
259 | ### Changed
260 | - lint source before running build
261 |
262 | ## [1.1.0]
263 |
264 | ### Added
265 | - default to 4.4
266 |
267 | ## [1.0.0]
268 |
269 | ### Changed
270 | - `isLoaded()` only returns true if the script tag has the `data-esri-loader` attribute
271 |
272 | ## [0.3.1]
273 |
274 | ### Fixed
275 | - fixed no callback bug
276 |
277 | ### Support
278 | - added unit tests
279 | - add a minified build and source maps for published releases
280 |
281 | ## [0.3.0]
282 |
283 | ### Added
284 | - add default export
285 |
286 | ### Fixed
287 | - build outputs es5/umd (main) and es5/esm (module)
288 |
289 | ## [0.2.0]
290 |
291 | ### Added
292 | - enable pre-loading
293 | - default to 4.3
294 |
295 | ## [0.1.3]
296 |
297 | ### Fixed
298 | - default to 4.2
299 | - use HTTPS by default
300 |
301 | ## [0.1.2]
302 |
303 | ### Fixed
304 | - finally got `import from 'esri-loader' working from Angular/TS apps
305 |
306 | ## 0.1.1
307 |
308 | ### Fixed
309 | - try to fix Error: Cannot find module "." in consuming TS apps
310 |
311 | ## 0.1.0
312 |
313 | ### Added
314 | - copied over source from angular-cli-esri and set up TS build
315 |
316 | [Unreleased]: https://github.com/Esri/esri-loader/compare/v3.7.0...HEAD
317 | [3.7.0]: https://github.com/Esri/esri-loader/compare/v3.6.0...v3.7.0
318 | [3.6.0]: https://github.com/Esri/esri-loader/compare/v3.5.0...v3.6.0
319 | [3.5.0]: https://github.com/Esri/esri-loader/compare/v3.4.0...v3.5.0
320 | [3.4.0]: https://github.com/Esri/esri-loader/compare/v3.3.0...v3.4.0
321 | [3.3.0]: https://github.com/Esri/esri-loader/compare/v3.2.0...v3.3.0
322 | [3.2.0]: https://github.com/Esri/esri-loader/compare/v3.1.0...v3.2.0
323 | [3.1.0]: https://github.com/Esri/esri-loader/compare/v3.0.0...v3.1.0
324 | [3.0.0]: https://github.com/Esri/esri-loader/compare/v2.16.0...v3.0.0
325 | [2.16.0]: https://github.com/Esri/esri-loader/compare/v2.15.0...v2.16.0
326 | [2.15.0]: https://github.com/Esri/esri-loader/compare/v2.14.0...v2.15.0
327 | [2.14.0]: https://github.com/Esri/esri-loader/compare/v2.13.0...v2.14.0
328 | [2.13.0]: https://github.com/Esri/esri-loader/compare/v2.12.0...v2.13.0
329 | [2.12.0]: https://github.com/Esri/esri-loader/compare/v2.11.0...v2.12.0
330 | [2.11.0]: https://github.com/Esri/esri-loader/compare/v2.10.2...v2.11.0
331 | [2.10.2]: https://github.com/Esri/esri-loader/compare/v2.10.1...v2.10.2
332 | [2.10.1]: https://github.com/Esri/esri-loader/compare/v2.10.0...v2.10.1
333 | [2.10.0]: https://github.com/Esri/esri-loader/compare/v2.9.2...v2.10.0
334 | [2.9.2]: https://github.com/Esri/esri-loader/compare/v2.9.1...v2.9.2
335 | [2.9.1]: https://github.com/Esri/esri-loader/compare/v2.9.0...v2.9.1
336 | [2.9.0]: https://github.com/Esri/esri-loader/compare/v2.8.0...v2.9.0
337 | [2.8.0]: https://github.com/Esri/esri-loader/compare/v2.7.0...v2.8.0
338 | [2.7.0]: https://github.com/Esri/esri-loader/compare/v2.6.0...v2.7.0
339 | [2.6.0]: https://github.com/Esri/esri-loader/compare/v2.5.0...v2.6.0
340 | [2.5.0]: https://github.com/Esri/esri-loader/compare/v2.4.0...v2.5.0
341 | [2.4.0]: https://github.com/Esri/esri-loader/compare/v2.3.0...v2.4.0
342 | [2.3.0]: https://github.com/Esri/esri-loader/compare/v2.2.0...v2.3.0
343 | [2.2.0]: https://github.com/Esri/esri-loader/compare/v2.1.0...v2.2.0
344 | [2.1.0]: https://github.com/Esri/esri-loader/compare/v2.0.0...v2.1.0
345 | [2.0.0]: https://github.com/Esri/esri-loader/compare/v1.7.0...v2.0.0
346 | [1.7.0]: https://github.com/Esri/esri-loader/compare/v1.6.2...v1.7.0
347 | [1.6.2]: https://github.com/Esri/esri-loader/compare/v1.6.1...v1.6.2
348 | [1.6.1]: https://github.com/Esri/esri-loader/compare/v1.6.0...v1.6.1
349 | [1.6.0]: https://github.com/Esri/esri-loader/compare/v1.5.3...v1.6.0
350 | [1.5.3]: https://github.com/Esri/esri-loader/compare/v1.5.2...v1.5.3
351 | [1.5.2]: https://github.com/Esri/esri-loader/compare/v1.5.1...v1.5.2
352 | [1.5.1]: https://github.com/Esri/esri-loader/compare/v1.5.0...v1.5.1
353 | [1.5.0]: https://github.com/Esri/esri-loader/compare/v1.4.0...v1.5.0
354 | [1.4.0]: https://github.com/Esri/esri-loader/compare/v1.3.0...v1.4.0
355 | [1.3.0]: https://github.com/Esri/esri-loader/compare/v1.2.1...v1.3.0
356 | [1.2.1]: https://github.com/Esri/esri-loader/compare/v1.2.0...v1.2.1
357 | [1.2.0]: https://github.com/Esri/esri-loader/compare/v1.1.0...v1.2.0
358 | [1.1.0]: https://github.com/Esri/esri-loader/compare/v1.0.0...v1.1.0
359 | [1.0.0]: https://github.com/Esri/esri-loader/compare/v0.3.1...v1.0.0
360 | [0.3.1]: https://github.com/Esri/esri-loader/compare/v0.3.0...v0.3.1
361 | [0.3.0]: https://github.com/Esri/esri-loader/compare/v0.2.0...v0.3.0
362 | [0.2.0]: https://github.com/Esri/esri-loader/compare/v0.1.3...v0.2.0
363 | [0.1.3]: https://github.com/Esri/esri-loader/compare/v0.1.2...v0.1.3
364 | [0.1.2]: https://github.com/Esri/esri-loader/tree/v0.1.2
365 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # esri-loader (Deprecated)
2 |
3 | [](http://github.com/badges/stability-badges) [](https://github.com/Esri/esri-loader/releases) [](https://www.npmjs.com/package/esri-loader) [](https://github.com/Esri/esri-loader/blob/master/LICENSE) [](https://github.com/Esri/esri-loader/stargazers)
4 |
5 | A tiny library to help you use the [ArcGIS Maps SDK for JavaScript](https://developers.arcgis.com/javascript/latest/tooling-intro/) AMD modules in applications built with popular JavaScript frameworks and bundlers.
6 |
7 | **Deprecation Notice:** The esri-loader npm package is deprecated at Maps SDK for JavaScript version 4.29 and will be retired at version 4.31. Locally built applications should use the [@arcgis/core](https://developers.arcgis.com/javascript/latest/es-modules/) ES modules npm package. Here are [sample applications](https://github.com/Esri/jsapi-resources/tree/main/esm-samples) for getting started. For more information see the [building with ES Modules](https://developers.arcgis.com/javascript/latest/guide/es-modules) guide topic.
8 |
9 | Ready to jump in? Follow the [Install](#install) and [Usage](#usage) instructions below to get started. Then see more in depth instructions on how to [configure esri-loader](#configuring-esri-loader).
10 |
11 | Want to learn more? Learn how esri-loader can help [improve application load performance](#lazy-loading-the-arcgis-api-for-javascript) and allow you to [use the Maps SDK in server side rendered applications](#server-side-rendering).
12 |
13 | Looking for legacy examples from a variety of frameworks, or 3.x information? Visit the [archive](archived-examples.md) page.
14 |
15 | ## Table of Contents
16 | - [Known Limitations](#known-limitations)
17 | - [Install](#install)
18 | - [Usage](#usage)
19 | - [Loading Modules from the ArcGIS Maps SDK for JavaScript](#loading-modules-from-the-arcgis-api-for-javascript)
20 | - [Lazy Loading the ArcGIS Maps SDK for JavaScript](#lazy-loading-the-arcgis-api-for-javascript)
21 | - [Loading Styles](#loading-styles)
22 | - [Do I need esri-loader?](#do-i-need-esri-loader)
23 | - [Advanced Usage](#advanced-usage)
24 | - [ArcGIS Types](#arcgis-types)
25 | - [esri-loader-typings-helper Plugin](#esri-loader-typings-helper-plugin)
26 | - [Configuring esri-loader](#configuring-esri-loader)
27 | - [Configuring Dojo](#configuring-dojo)
28 | - [Overriding ArcGIS Styles](#overriding-arcgis-styles)
29 | - [Pre-loading the ArcGIS Maps SDK for JavaScript](#pre-loading-the-arcgis-api-for-javascript)
30 | - [Using your own script tag](#using-your-own-script-tag)
31 | - [Without a module bundler](#without-a-module-bundler)
32 | - [Using a module script tag](#using-a-module-script-tag)
33 | - [Using the esriLoader Global](#using-the-esriloader-global)
34 | - [Pro Tips](#pro-tips)
35 | - [Using Classes Synchronously](#using-classes-synchronously)
36 | - [Server Side Rendering](#server-side-rendering)
37 | - [FAQs](#faqs)
38 | - [Updating from previous versions](#updating-from-previous-versions)
39 | - [From < v1.5](#from--v15)
40 | - [From angular-esri-loader](#from-angular-esri-loader)
41 | - [Dependencies](#dependencies)
42 | - [Browsers](#browsers)
43 | - [Promises](#promises)
44 | - [Issues](#issues)
45 | - [Contributing](#contributing)
46 | - [Licensing](#licensing)
47 |
48 | ## Known Limitations
49 |
50 |
51 | - Compatibility with frameworks that don't support native [async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises#async_and_await) in AMD modules at runtime was removed at 4.27 (June 2023). In particular, this affects Angular applications using esri-loader because async/await is [not supported in Zone.js](https://angular.io/guide/roadmap#improve-runtime-performance-and-developer-experience-with-a-new-reactivity-model). Angular users that run into async/await-related issues will need to migrate off Zone.js or move from AMD modules to using [@arcgis/core ES modules](https://developers.arcgis.com/javascript/latest/es-modules/) in order to continue using the latest releases of the SDK.
52 |
53 | ## Install
54 |
55 | ```bash
56 | npm install --save esri-loader
57 | ```
58 |
59 | or
60 |
61 | ```bash
62 | yarn add esri-loader
63 | ```
64 |
65 | ## Usage
66 |
67 | The code snippets below show how to load the ArcGIS Maps SDK for JavaScript and its modules and then use them to create a map. Where you would place similar code in your application will depend on which application framework you are using.
68 |
69 | ### Loading Modules from the ArcGIS Maps SDK for JavaScript
70 |
71 | #### From the Latest Version
72 |
73 | Here's an example of how you could load and use the `WebMap` and `MapView` classes from the latest 4.x release to create a map (based on [this sample](https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=webmap-basic)):
74 |
75 | ```js
76 | import { loadModules } from 'esri-loader';
77 |
78 | // this will lazy load the SDK
79 | // and then use Dojo's loader to require the classes
80 | loadModules(['esri/views/MapView', 'esri/WebMap'])
81 | .then(([MapView, WebMap]) => {
82 | // then we load a web map from an id
83 | const webmap = new WebMap({
84 | portalItem: { // autocasts as new PortalItem()
85 | id: 'f2e9b762544945f390ca4ac3671cfa72'
86 | }
87 | });
88 | // and we show that map in a container w/ id #viewDiv
89 | const view = new MapView({
90 | map: webmap,
91 | container: 'viewDiv'
92 | });
93 | })
94 | .catch(err => {
95 | // handle any errors
96 | console.error(err);
97 | });
98 | ```
99 |
100 | #### From a Specific Version
101 |
102 | By default esri-loader will load modules from the [latest 4.x release of the SDK from the CDN](https://developers.arcgis.com/javascript/latest/guide/get-api/#cdn), but you can [configure the default behavior](#configuring-esri-loader) by calling `setDefaultOptions()` once _before_ making any calls to `loadModules()`.
103 |
104 | ```js
105 | // app.js
106 | import { setDefaultOptions } from 'esri-loader';
107 |
108 | // configure esri-loader to use version 4.24 from the ArcGIS CDN
109 | // NOTE: make sure this is called once before any calls to loadModules()
110 | setDefaultOptions({ version: '4.24' })
111 | ```
112 |
113 | Then later, for example after a map component has mounted, you would use `loadModules()` as normal.
114 |
115 | ```js
116 | // component.js
117 | import { loadModules } from 'esri-loader';
118 |
119 | // this will lazy load the SDK
120 | // and then use Dojo's loader to require the map class
121 | loadModules(['esri/map'])
122 | .then(([Map]) => {
123 | // create map with the given options at a DOM node w/ id 'mapNode'
124 | let map = new Map('mapNode', {
125 | center: [-118, 34.5],
126 | zoom: 8,
127 | basemap: 'dark-gray'
128 | });
129 | })
130 | .catch(err => {
131 | // handle any script or module loading errors
132 | console.error(err);
133 | });
134 | ```
135 |
136 | You can load the ["next" version of the SDK](https://github.com/Esri/feedback-js-api-next#esri-loader) by passing `version: 'next'`.
137 |
138 | #### From a Specific URL
139 |
140 | If you want to load modules from a build that you host on your own server (i.e. that you've [downloaded](https://developers.arcgis.com/javascript/latest/guide/get-api/#download-api) or [built with Dojo](https://developers.arcgis.com/javascript/latest/guide/using-npm/)), you would set the default `url` option instead:
141 |
142 | ```js
143 | // app.js
144 | import { setDefaultOptions } from 'esri-loader';
145 |
146 | // configure esri-loader to use version from a locally hosted build of the SDK
147 | // NOTE: make sure this is called once before any calls to loadModules()
148 | setDefaultOptions({ url: `http://server/path/to/esri` });
149 | ```
150 |
151 | See [Configuring esri-loader](#configuring-esri-loader) for all available configuration options.
152 |
153 | ### Lazy Loading
154 |
155 | Lazy loading the modules can dramatically improve the initial load performance of your mapping application, especially if your users may never end up visiting any routes that need to show a map or 3D scene. That is why it is the default behavior of esri-loader. In the above snippets, the first time `loadModules()` is called, it will lazy load the modules by injecting a `
382 | ```
383 |
384 | ### Without a module bundler
385 |
386 | Typically you would [install the esri-loader package](#install) and then use a module loader/bundler to `import` the functions you need as part of your application's build. However, ES5 builds of esri-loader are also distributed on [UNPKG](https://unpkg.com/) both as ES modules and as a [UMD](http://jargon.js.org/_glossary/UMD.md) bundle that exposes the `esriLoader` global.
387 |
388 | This is an _excellent_ way to prototype how you will use the ArcGIS Maps SDK for JavaScript, or to isolate any problems that you are having with the SDK. Before we can help you with any issue related to the behavior of a map, scene, or widgets, we will **require** you to reproduce it _outside_ your application. A great place to start is one of the codepens linked below.
389 |
390 | #### Using a module script tag
391 |
392 | You can load the esri-loader [ES modules directly in modern browsers](https://caniuse.com/#feat=es6-module) using `
406 | ```
407 |
408 | You can fork [this codepen](https://codepen.io/gavinr/pen/wvavjwp) to try this out yourself.
409 |
410 | A disadvantage of this approach is that the ES module build of esri-loader is not bundled. This means your browser will make multiple requests for a few (tiny) JS files, which may not be suitable for a production application.
411 |
412 | #### Using the esriLoader Global
413 |
414 | If you need to run the script in an older browser, you can load the UMD build and then use the `esriLoader` global.
415 |
416 | ```html
417 |
421 |
422 |
428 | ```
429 |
430 | You can fork [this codepen](https://codepen.io/tomwayson/pen/PoqwZYm) to try this out yourself.
431 |
432 | ## Pro Tips
433 |
434 | ### Using Classes Synchronously
435 |
436 | Let's say you need to create a map in one component, and then later in another component add a graphic to that map. Unlike creating a map, creating a graphic and adding it to a map is ordinarily a synchronous operation, so it can be inconvenient to have to wait for `loadModules()` just to load the `Graphic` class. One way to handle this is have the function that creates the map _also_ load the `Graphic` class before its needed. You can then hold onto that class for later use to be exposed by a function like `addGraphicToMap(view, graphicJson)`:
437 |
438 | ```javascript
439 | // utils/map.js
440 | import { loadModules } from 'esri-loader';
441 |
442 | // NOTE: module, not global scope
443 | let _Graphic;
444 |
445 | // this will be called by the map component
446 | export function loadMap(element, mapOptions) {
447 | return loadModules(['esri/Map', 'esri/views/MapView', 'esri/Graphic'])
448 | .then(([Map, MapView, Graphic]) => {
449 | // hold onto the graphic class for later use
450 | _Graphic = Graphic;
451 | // create the Map
452 | const map = new Map(mapOptions);
453 | // return a view showing the map at the element
454 | return new MapView({
455 | map,
456 | container: element
457 | });
458 | });
459 | }
460 |
461 | // this will be called by the component that needs to add the graphic to the map
462 | export function addGraphicToMap(view, graphicJson) {
463 | // make sure that the graphic class has already been loaded
464 | if (!_Graphic) {
465 | throw new Error('You must load a map before creating new graphics');
466 | }
467 | view.graphics.add(new _Graphic(graphicJson));
468 | }
469 | ```
470 |
471 | You can [see this pattern in use in a real-world application](https://github.com/tomwayson/create-arcgis-app/blob/master/src/utils/map.js).
472 |
473 | See [#124 (comment)](https://github.com/Esri/esri-loader/issues/124#issuecomment-408482410) and [#71 (comment)](https://github.com/Esri/esri-loader/issues/71#issuecomment-381356848) for more background on this pattern.
474 |
475 | ### Server Side Rendering
476 |
477 | This library also allows you to use the SDK in [applications that are rendered on the server](https://medium.com/@baphemot/whats-server-side-rendering-and-do-i-need-it-cb42dc059b38). There's really no difference in how you invoke the functions exposed by this library, however you should avoid trying to call them from any code that runs on the server. The easiest way to do this is to call `loadModules()` in component lifecyle hooks that are only invoked in a browser, for example, React's [useEffect](https://reactjs.org/docs/hooks-effect.html) or [componentDidMount](https://reactjs.org/docs/react-component.html#componentdidmount), or Vue's [mounted](https://vuejs.org/v2/api/#mounted).
478 |
479 | Alternatively, you could use checks like the following to prevent calling esri-loader functions on the server:
480 |
481 | ```js
482 | import { loadCss } from 'esri-loader';
483 |
484 | if (typeof window !== 'undefined') {
485 | // this is running in a browser, so go ahead and load the CSS
486 | loadCss();
487 | }
488 | ```
489 |
490 | See [next-arcgis-app](https://github.com/tomwayson/next-arcgis-app/) or [esri-loader-react-starter-kit](https://github.com/tomwayson/esri-loader-react-starter-kit/) for examples of how to use esri-loader in server side rendered (SSR) applications.
491 |
492 | ### FAQs
493 |
494 | In addition to the pro tips above, you might want to check out some [frequently asked questions](https://github.com/Esri/esri-loader/issues?utf8=%E2%9C%93&q=label%3AFAQ+sort%3Aupdated-desc).
495 |
496 | ## Updating from previous versions
497 |
498 | ### From < v1.5
499 |
500 | If you have an application using a version that is less than v1.5, [this commit](https://github.com/odoe/vue-jsapi4/pull/1/commits/4cb6413c0ea31fdd09e94f3a0ce0d1669a9fd5ad) shows the kinds of changes you'll need to make. In most cases, you should be able to replace a series of calls to `isLoaded()`, `bootstrap()`, and `dojoRequire()` with a single call to `loadModules()`.
501 |
502 | ### From angular-esri-loader
503 |
504 | The angular-esri-loader wrapper library is no longer needed and has been deprecated in favor of using esri-loader directly. See [this issue](https://github.com/Esri/esri-loader/issues/75) for suggestions on how to replace angular-esri-loader with the latest version of esri-loader.
505 |
506 | ## Dependencies
507 |
508 | ### Browsers
509 |
510 | This library doesn't have any external dependencies, but the functions it exposes to load the SDK and its modules expect to be run in a browser. This library officially supports [the same browsers that are supported by the latest version of the ArcGIS Maps SDK for JavaScript](https://developers.arcgis.com/javascript/latest/guide/system-requirements/index.html#supported-browsers).
511 |
512 | You cannot use this helper library in [Node.js](https://nodejs.org/), but you _can_ use this library in [server side rendered applications](#server-side-rendering) as well as [Electron](#electron). If you need to execute requests to ArcGIS REST services from something like a Node.js CLI application, see [ArcGIS Rest JS](https://developers.arcgis.com/arcgis-rest-js/).
513 |
514 | ### Promises
515 |
516 | The asynchronous functions like `loadModules()` and `loadScript()` return [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)s, so if your application has to support [browsers that don't support Promise (i.e. IE)](https://caniuse.com/#search=promise) you have a few options.
517 |
518 | If there's already a Promise implementation loaded on the page you can configure esri-loader to use that implementation. For example, in [ember-esri-loader](https://github.com/Esri/ember-esri-loader), we configure esri-loader to use the RSVP Promise implementation included with Ember.js.
519 |
520 | ```js
521 | import { utils } from 'esri-loader';
522 |
523 | init () {
524 | this._super(...arguments);
525 | // have esriLoader use Ember's RSVP promise
526 | utils.Promise = Ember.RSVP.Promise;
527 | },
528 | ```
529 |
530 | Otherwise, you should consider using a [Promise polyfill](https://www.google.com/search?q=promise+polyfill), ideally [only when needed](https://philipwalton.com/articles/loading-polyfills-only-when-needed/).
531 |
532 | ## Licensing
533 |
534 | Copyright © 2016-2022 Esri
535 |
536 | Licensed under the Apache License, Version 2.0 (the "License");
537 | you may not use this file except in compliance with the License.
538 | You may obtain a copy of the License at
539 |
540 | https://www.apache.org/licenses/LICENSE-2.0
541 |
542 | Unless required by applicable law or agreed to in writing, software
543 | distributed under the License is distributed on an "AS IS" BASIS,
544 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
545 | See the License for the specific language governing permissions and
546 | limitations under the License.
547 |
548 | A copy of the license is available in the repository's [LICENSE]( ./LICENSE) file.
549 |
--------------------------------------------------------------------------------
/archived-examples.md:
--------------------------------------------------------------------------------
1 | ### 3.x Types
2 |
3 | You can use these instructions in the [Legacy samples for ArcGIS Maps SDK for JavaScript Resources](https://github.com/Esri/jsapi-resources/releases/tag/legacy) to install the 3.x types. Follow the instructions outlined in the `/3.x/typescript` directory.
4 |
5 | Use `import * as esri from 'esri';` to implement the types [as shown here](https://github.com/Esri/angular-cli-esri-map/issues/17#issue-360490589).
6 |
7 | ```ts
8 | // define a type that is an array of the 3.x types you are using
9 | // and indicate that loadModules() will resolve with that type
10 | type MapModules = [typeof import("esri/map"), typeof import("esri/geometry/Extent")];
11 | const [Map, Extent] = await (loadModules(["esri/map", "esri/geometry/Extent"]) as Promise);
12 | // the returned objects now have type
13 | let map = new Map("viewDiv"...
14 | ```
15 |
16 | A more complete 3.x sample can be [seen here](https://codesandbox.io/s/rj6jloy4nm?fontsize=14&module=%2Fsrc%2Fmapping.ts).
17 |
18 | ### 4.x Types
19 |
20 | A more complete 4.x sample can be [seen here](https://codesandbox.io/s/xv8mw2890w?fontsize=14&module=%2Fsrc%2Fmapping.ts).
21 |
22 | ### Legacy browsers
23 |
24 | Since this library also works with [v3.x of the ArcGIS Maps SDK](https://developers.arcgis.com/javascript/3/), the community [has made some effort](https://github.com/Esri/esri-loader/pull/67) to get it to work with [some of the older browsers supported by 3.x](https://developers.arcgis.com/javascript/3/jshelp/supported_browsers.html) like IE < 11.
25 |
26 | ### Legacy examples
27 |
28 | Here is an archive of some applications and framework-specific wrapper libraries that use this library. Most of these examples haven't been updated in a long time, so check the version of esri-loader and their commit history before using them as a reference. They are presented by framework in alphabetical order - not picking any favorites here :stuck_out_tongue_winking_eye::
29 |
30 | ### [Angular](https://angular.io/)
31 |
32 | #### Reusable libraries for Angular
33 |
34 | - [angular-esri-components](https://github.com/TheKeithStewart/angular-esri-components) - A set of Angular components to work with ArcGIS Maps SDK for JavaScript v4.3
35 |
36 | #### Example Angular applications
37 |
38 | - [angular-cli-esri-map](https://github.com/Esri/angular-cli-esri-map) - Example of how to build a simple mapping component using Angular CLI.
39 |
40 | ### [CanJS](https://canjs.com/)
41 |
42 | - [can-arcgis](https://github.com/roemhildtg/can-arcgis) - CanJS configurable mapping app (inspired by [cmv-app](https://github.com/cmv/cmv-app)) and components built for the ArcGIS Maps SDK for JavaScript 4.x, bundled with [StealJS](https://stealjs.com/)
43 |
44 | ### [Choo](https://choo.io/)
45 |
46 | - [esri-choo-example](https://github.com/jwasilgeo/esri-choo-example) - An example Choo application that shows how to use esri-loader to create a custom map view.
47 |
48 | ### [Dojo 2+](https://dojo.io)
49 |
50 | - [dojo-esri-loader](https://github.com/odoe/dojo-esri-loader) - Dojo 5 app with esri-loader ([blog post](https://odoe.net/blog/dojo-framework-with-arcgis-api-for-javascript/))
51 |
52 | - [esri-dojo](https://github.com/jamesmilneruk/esri-dojo) - An example of how to use Esri Loader with Dojo 2+. This example is a simple map that allows you to place markers on it.
53 |
54 | ### [Electron](https://electron.atom.io/)
55 |
56 | - [ng-cli-electron-esri](https://github.com/TheKeithStewart/ng-cli-electron-esri) - This project is meant to demonstrate how to run a mapping application using the ArcGIS Maps SDK for JavaScript inside of Electron
57 |
58 | #### Reusable libraries for Ember
59 |
60 | - [ember-esri-loader](https://github.com/Esri/ember-esri-loader) - An Ember addon that wraps this library
61 |
62 | #### Example Ember applications
63 |
64 | See the [examples over at ember-esri-loader](https://github.com/Esri/ember-esri-loader/#examples)
65 |
66 | ### [Glimmer.js](https://glimmerjs.com/)
67 |
68 | - [esri-glimmer-example](https://github.com/tomwayson/esri-glimmer-example) - An example of how to use the ArcGIS Maps SDK for JavaScript in a https://glimmerjs.com/ application
69 |
70 | ### [Hyperapp](https://hyperapp.js.org/)
71 |
72 | - [esri-hyperapp-example](https://github.com/jwasilgeo/esri-hyperapp-example) - An example Hyperapp application that shows how to use esri-loader to create a custom map view and component.
73 |
74 | ### [Preact](https://github.com/developit/preact)
75 |
76 | - [esri-preact-pwa](https://github.com/tomwayson/esri-preact-pwa) - An example progressive web app (PWA) using the ArcGIS Maps SDK for JavaScript built with Preact
77 |
78 | #### Reusable libraries for React
79 |
80 | - [esri-loader-hooks](https://github.com/tomwayson/esri-loader-hooks) - Custom React hooks for using the ArcGIS Maps SDK for JavaScript with esri-loader
81 | - [react-arcgis](https://github.com/Esri/react-arcgis) - A few components to help you get started using esri-loader with React
82 | - [esri-loader-react](https://github.com/davetimmins/esri-loader-react) - A React component wrapper around esri-loader ([blog post](https://davetimmins.github.io/2017/07/19/esri-loader-react/))
83 | - [arcgis-react-redux-legend](https://github.com/davetimmins/arcgis-react-redux-legend) - Legend control for ArcGIS JS v4 using React and Redux
84 |
85 | #### Example React applications
86 | - [create-arcgis-app](https://github.com/tomwayson/create-arcgis-app/) - An example of how to use the ArcGIS platform in an application created with Create React App and React Router.
87 | - [next-arcgis-app](https://github.com/tomwayson/next-arcgis-app/) - An example of how to use the ArcGIS platform in an application built with Next.js
88 | - [esri-loader-react-starter-kit](https://github.com/tomwayson/esri-loader-react-starter-kit) - A fork of the [react-starter-kit](https://github.com/kriasoft/react-starter-kit) showing how to use esri-loader in an isomorphic/universal React application
89 | - [create-react-app-esri-loader](https://github.com/davetimmins/create-react-app-esri-loader/) - An example create-react-app application that uses [esri-loader-react](https://github.com/davetimmins/esri-loader-react) to load the ArcGIS Maps SDK for JavaScript
90 | - [React-Typescript-App-with-ArcGIS-JSAPI](https://github.com/guzhongren/React-Typescript-App-with-ArcGIS-JSAPI) - An example create-react-app application that uses [esri-loader](https://github.com/Esri/esri-loader), [esri-loader-react](https://github.com/davetimmins/esri-loader-react), [Typescript](https://www.typescriptlang.org/), [Webpack3](https://webpack.js.org/) to create MapView
91 |
92 | ### [Riot](https://riot.js.org/)
93 |
94 | - [esri-riot-example](https://github.com/jwasilgeo/esri-riot-example) - An example Riot application that shows how to use esri-loader to create a custom `` component.
95 |
96 | ### [Stencil](https://stenciljs.com/)
97 |
98 | - [esri-stencil-example](https://github.com/Dzeneralen/esri-stencil-example) - An example Stencil application that shows how to use esri-loader to create a custom map view component and implement some basic routing controlling the map state
99 |
100 | ### [Svelte](https://svelte.dev/)
101 |
102 | - [esri-svelte-example](https://github.com/gavinr/esri-svelte-example) - An example Svelte application that shows how to use esri-loader to load a map.
103 | - [esri-svelte-basemaps-example](https://github.com/jwasilgeo/esri-svelte-basemaps-example) - An example Svelte application that shows how to use esri-loader to create a custom `` component and explore various basemaps.
104 |
105 | ### [Vue.js](https://vuejs.org/)
106 |
107 | - [CreateMap](https://github.com/oppoudel/CreateMap) - Create Map: City of Baltimore - https://gis.baltimorecity.gov/createmap/#/
108 | - [City of Baltimore: Map Gallery](https://github.com/oppoudel/MapGallery_Vue) - Map Gallery built with Vue.js that uses this library to load the ArcGIS SDK
109 | - [vue-jsapi4](https://github.com/odoe/vue-jsapi4) - An example of how to use the [ArcGIS Maps SDK for Javascript](https://developers.arcgis.com/javascript/) in a [NUXT](https://nuxtjs.org/) application ([blog post](https://odoe.net/blog/arcgis-api-4-for-js-with-vue-cli-and-nuxt/), [video](https://youtu.be/hqJzzgM8seo))
110 | - [esri-vue-cli-example](https://github.com/tomwayson/esri-vue-cli-example) - An example of how to use the [ArcGIS Maps SDK for JavaScript 3.x](https://developers.arcgis.com/javascript/3/) in a [vue-cli](https://github.com/vuejs/vue-cli) application
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Wed Mar 08 2017 13:05:58 GMT-0800 (PST)
3 |
4 | module.exports = function(config) {
5 | var configuration = {
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine', 'karma-typescript'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | // source code and tests
19 | 'src/**/*.ts',
20 | // test helpers
21 | 'test/**/*.ts',
22 | // serve mock scripts
23 | { pattern: 'test/mocks/*.js', included: false }
24 | ],
25 |
26 |
27 | // list of files to exclude
28 | exclude: [
29 | ],
30 |
31 |
32 | // preprocess matching files before serving them to the browser
33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
34 | preprocessors: {
35 | '**/*.ts': 'karma-typescript',
36 | 'src/**/!(*test).ts': 'coverage'
37 | },
38 |
39 |
40 | // test results reporter to use
41 | // possible values: 'dots', 'progress'
42 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
43 | reporters: ['mocha', 'karma-typescript'],
44 |
45 | // web server port
46 | port: 9876,
47 |
48 |
49 | // enable / disable colors in the output (reporters and logs)
50 | colors: true,
51 |
52 |
53 | // level of logging
54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
55 | logLevel: config.LOG_INFO,
56 |
57 |
58 | // enable / disable watching file and executing tests whenever any file changes
59 | autoWatch: true,
60 |
61 |
62 | // start these browsers
63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
64 | browsers: ['Chrome'],
65 |
66 |
67 | // Continuous Integration mode
68 | // if true, Karma captures browsers, runs the tests and exits
69 | singleRun: false,
70 |
71 | // Concurrency level
72 | // how many browser should be started simultaneous
73 | concurrency: Infinity,
74 |
75 | coverageReporter: {
76 | type : 'text',
77 | dir : 'coverage/'
78 | },
79 |
80 | customLaunchers: {
81 | 'FirefoxHeadless': {
82 | base: 'Firefox',
83 | flags: [
84 | '-headless',
85 | ],
86 | }
87 | }
88 | };
89 |
90 | config.set(configuration);
91 | };
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "esri-loader",
3 | "version": "3.7.0",
4 | "description": "A tiny library to help load ArcGIS Maps SDK for JavaScript modules in non-Dojo applications",
5 | "files": [
6 | "dist"
7 | ],
8 | "main": "dist/umd/esri-loader.js",
9 | "browser": "dist/umd/esri-loader.js",
10 | "module": "dist/esm/esri-loader.js",
11 | "js:next": "dist/esm/esri-loader.js",
12 | "types": "dist/esm/esri-loader.d.ts",
13 | "scripts": {
14 | "build": "npm run compile && npm run bundle",
15 | "bundle": "rollup -c",
16 | "build:release": "npm run build && npm run bundle -- profiles/prod.config.js",
17 | "compile": "tsc",
18 | "ci": "karma start --single-run=true --browsers FirefoxHeadless",
19 | "clean": "rimraf dist && mkdirp dist",
20 | "lint": "tslint -c tslint.json 'src/esri-loader.ts'",
21 | "prebuild:release": "npm run clean",
22 | "precompile": "npm run lint",
23 | "prepublish": "npm run build:release",
24 | "preversion": "npm run test && git add README.md CHANGELOG.md",
25 | "start": "karma start",
26 | "test": "karma start --single-run=true --browsers Chrome,Firefox"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/Esri/esri-loader.git"
31 | },
32 | "keywords": [
33 | "Esri",
34 | "ArcGIS",
35 | "JavaScript",
36 | "module",
37 | "loader",
38 | "Dojo"
39 | ],
40 | "author": "Tom Wayson (https://tomwayson.com)",
41 | "license": "Apache-2.0",
42 | "bugs": {
43 | "url": "https://github.com/Esri/esri-loader/issues"
44 | },
45 | "homepage": "https://github.com/Esri/esri-loader",
46 | "devDependencies": {
47 | "@types/jasmine": "^2.8.11",
48 | "concurrently": "^3.4.0",
49 | "jasmine-core": "^2.8.0",
50 | "karma": "^6.3.16",
51 | "karma-chrome-launcher": "^2.0.0",
52 | "karma-coverage": "^1.1.2",
53 | "karma-firefox-launcher": "^1.1.0",
54 | "karma-jasmine": "^1.1.0",
55 | "karma-mocha-reporter": "^2.2.3",
56 | "karma-typescript": "^5.5.3",
57 | "mkdirp": "^0.5.1",
58 | "onchange": "^3.2.1",
59 | "rimraf": "^2.6.2",
60 | "rollup": "^0.41.6",
61 | "rollup-plugin-uglify": "^2.0.1",
62 | "tslint": "^5.7.0",
63 | "typescript": "^4.6.3"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/profiles/base.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | entry: 'dist/esm/esri-loader.js',
3 | format: 'umd',
4 | moduleName: 'esriLoader',
5 | exports: 'named',
6 | dest: 'dist/umd/esri-loader.js',
7 | sourceMap: true
8 | };
9 |
--------------------------------------------------------------------------------
/profiles/prod.config.js:
--------------------------------------------------------------------------------
1 | import uglify from 'rollup-plugin-uglify';
2 | import base from './base.config.js';
3 |
4 | export default Object.assign({}, base, {
5 | dest: 'dist/umd/esri-loader.min.js',
6 | plugins: [
7 | uglify()
8 | ]
9 | });
10 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import base from './profiles/base.config.js';
2 |
3 | export default base;
4 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | CHANGE="$1"
4 |
5 | # update CHANGELOG
6 | carriage-return --level "$CHANGE"
7 |
8 | # bump version
9 | # NOTE: force is needed so that the preversion script can run
10 | # and preversion creates production build and runs tests
11 | npm version "$CHANGE" --force
12 |
13 | # push version commit and tags to github
14 | git push --follow-tags
15 |
16 | # publish to npm
17 | # TODO: uncomment this once this script is locked and loaded
18 | # npm publish
19 |
--------------------------------------------------------------------------------
/src/esri-loader.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2022 Esri
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | // re-export the functions that are part of the public API
15 | import utils from './utils/index';
16 | export { loadModules } from './modules';
17 | export { getScript, isLoaded, loadScript, setDefaultOptions } from './script';
18 | export { ILoadScriptOptions } from './script';
19 | export { loadCss } from './utils/css';
20 | export { utils };
21 |
--------------------------------------------------------------------------------
/src/modules.test.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable only-arrow-functions */
2 | import { jaspi3xUrl, removeRequire, stubRequire } from '../test/helpers';
3 | import { loadModules } from './modules';
4 | // TODO: mock this
5 | import { loadScript } from './script';
6 |
7 | describe('when loading modules', function() {
8 | const expectedModuleNames = ['esri/map', 'esri/layers/VectorTileLayer'];
9 | describe('when script has been loaded', function() {
10 | beforeEach(function() {
11 | // stub window require
12 | stubRequire();
13 | });
14 | it('should have registered an error handler', function(done) {
15 | spyOn(window.require, 'on').and.callThrough();
16 | loadModules(expectedModuleNames)
17 | .then(() => {
18 | expect(window.require.on.calls.argsFor(0)[0]).toEqual('error');
19 | done();
20 | })
21 | .catch((err) => {
22 | done.fail('call to loadModules should not have failed with: ' + err);
23 | });
24 | });
25 | it('should call require w/ correct args', function(done) {
26 | spyOn(window, 'require').and.callThrough();
27 | loadModules(expectedModuleNames)
28 | .then(() => {
29 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames);
30 | done();
31 | })
32 | .catch((err) => {
33 | done.fail('call to loadModules should not have failed with: ' + err);
34 | });
35 | });
36 | afterEach(function() {
37 | // clean up
38 | removeRequire();
39 | });
40 | });
41 | describe('when the script has not yet been loaded', function() {
42 | beforeEach(function() {
43 | // uh oh, not sure why this is needed
44 | // seems like some test above did not clean up after itself
45 | // but I can't find where
46 | // TODO: remove this line
47 | removeRequire();
48 | // w/o it, test fails w/
49 | // TypeError: Cannot read property 'argsFor' of undefined
50 | // b/c require is defined so it's not trying to add the script
51 | // and doesn't enter the appendChild spyOn() block below
52 | });
53 | describe('when there has been no attempt to load the script yet', function() {
54 | it('should not reject', function(done) {
55 | spyOn(document.body, 'appendChild').and.callFake(function(el) {
56 | stubRequire();
57 | spyOn(window, 'require').and.callThrough();
58 | // trigger the onload event listeners
59 | el.dispatchEvent(new Event('load'));
60 | });
61 | loadModules(expectedModuleNames, {
62 | url: jaspi3xUrl
63 | })
64 | .then(() => {
65 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames);
66 | done();
67 | })
68 | .catch((err) => {
69 | done.fail('call to loadModules should not have failed with: ' + err);
70 | });
71 | });
72 | });
73 | describe('when the script is still loading', function() {
74 | it('should not reject', function(done) {
75 | let scriptEl;
76 | spyOn(document.body, 'appendChild').and.callFake(function(el) {
77 | scriptEl = el;
78 | stubRequire();
79 | spyOn(window, 'require').and.callThrough();
80 | // trigger the onload event listeners
81 | el.dispatchEvent(new Event('load'));
82 | });
83 | spyOn(document, 'querySelector').and.callFake(function() {
84 | return scriptEl;
85 | });
86 | // load script using a non-default url
87 | loadScript({
88 | url: jaspi3xUrl
89 | });
90 | // don't wait for the script to load before trying to load modules
91 | loadModules(expectedModuleNames)
92 | .then(() => {
93 | expect(window.require.calls.argsFor(0)[0]).toEqual(expectedModuleNames);
94 | done();
95 | })
96 | .catch((err) => {
97 | done.fail('call to loadModules should not have failed with: ' + err);
98 | });
99 | });
100 | });
101 | afterEach(function() {
102 | // clean up
103 | removeRequire();
104 | });
105 | });
106 | });
107 |
--------------------------------------------------------------------------------
/src/modules.ts:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc.
2 | * Apache-2.0 */
3 |
4 | import { getScript, ILoadScriptOptions, isLoaded, loadScript } from './script';
5 | import utils from './utils/index';
6 |
7 | // wrap Dojo's require() in a promise
8 | function requireModules(modules: string[]): Promise {
9 | return new utils.Promise((resolve, reject) => {
10 | // If something goes wrong loading the esri/dojo scripts, reject with the error.
11 | const errorHandler = window['require'].on('error', reject);
12 | window['require'](modules, (...args) => {
13 | // remove error handler
14 | errorHandler.remove();
15 | // Resolve with the parameters from dojo require as an array.
16 | resolve(args as T);
17 | });
18 | });
19 | }
20 |
21 | // returns a promise that resolves with an array of the required modules
22 | // also will attempt to lazy load the API
23 | // if it has not already been loaded
24 | export function loadModules(modules: string[], loadScriptOptions: ILoadScriptOptions = {}): Promise {
25 | if (!isLoaded()) {
26 | // script is not yet loaded, is it in the process of loading?
27 | const script = getScript();
28 | const src = script && script.getAttribute('src');
29 | if (!loadScriptOptions.url && src) {
30 | // script is still loading and user did not specify a URL
31 | // in this case we want to default to the URL that's being loaded
32 | // instead of defaulting to the latest 4.x URL
33 | loadScriptOptions.url = src;
34 | }
35 | // attempt to load the script then load the modules
36 | return loadScript(loadScriptOptions).then(() => requireModules(modules));
37 | } else {
38 | // script is already loaded, just load the modules
39 | return requireModules(modules);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/script.test.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable only-arrow-functions */
2 | import { jaspi3xUrl, removeRequire, stubRequire } from '../test/helpers';
3 | import { isLoaded, loadScript, setDefaultOptions } from './script';
4 | import * as cssUtils from './utils/css';
5 |
6 | declare global {
7 | /* tslint:disable interface-name */
8 | interface Window {
9 | require?: any;
10 | stubRequire?: any;
11 | }
12 | /* tslint:enable interface-name */
13 | }
14 |
15 | // allow the mock scripts to emulate that the SDK has loaded
16 | window.stubRequire = stubRequire;
17 |
18 | // remove script tags added by esri-loader
19 | function removeScript() {
20 | const script = document.querySelector('script[data-esri-loader]');
21 | if (script) {
22 | script.parentElement.removeChild(script);
23 | }
24 | }
25 |
26 | // don't actually load the script or styles
27 | function fakeLoading() {
28 | spyOn(document.body, 'appendChild').and.callFake(function(el) {
29 | // trigger the onload event listeners
30 | el.dispatchEvent(new Event('load'));
31 | });
32 | spyOn(cssUtils, 'loadCss').and.stub();
33 | }
34 |
35 | describe('isLoaded', function() {
36 | describe('when has not yet been loaded', function() {
37 | beforeEach(function() {
38 | removeRequire();
39 | removeScript();
40 | });
41 | it('isLoaded should be false', function() {
42 | expect(isLoaded()).toBeFalsy();
43 | });
44 | });
45 | });
46 |
47 | describe('when loading the script', function() {
48 | describe('with library defaults', function() {
49 | let scriptEl;
50 | beforeAll(function(done) {
51 | fakeLoading();
52 | loadScript()
53 | .then((script) => {
54 | // hold onto script element for assertions below
55 | scriptEl = script;
56 | done();
57 | });
58 | });
59 | it('should default to latest version', function() {
60 | expect(scriptEl.src).toEqual('https://js.arcgis.com/4.25/');
61 | });
62 | it('should not have called loadCss', function() {
63 | expect((cssUtils.loadCss as jasmine.Spy).calls.any()).toBeFalsy();
64 | });
65 | });
66 | describe('with default loader options explicitly set', function() {
67 | const scriptUrl = 'http://server/path/to/esri';
68 | const cssUrl = `${scriptUrl}/css/main.css`;
69 | let scriptEl;
70 | beforeAll(function(done) {
71 | setDefaultOptions({
72 | url: scriptUrl,
73 | css: cssUrl
74 | });
75 | fakeLoading();
76 | loadScript()
77 | .then((script) => {
78 | // hold onto script element for assertions below
79 | scriptEl = script;
80 | done();
81 | });
82 | });
83 | it('should load the specified script url', function() {
84 | expect(scriptEl.src).toEqual(scriptUrl);
85 | });
86 | it('should have called loadCss', function() {
87 | expect((cssUtils.loadCss as jasmine.Spy).calls.any()).toBeTruthy();
88 | });
89 | it('should have called loadCss with the specified CSS url', function() {
90 | expect((cssUtils.loadCss as jasmine.Spy).calls.argsFor(0)[0]).toEqual(cssUrl);
91 | });
92 | afterAll(function() {
93 | setDefaultOptions(null);
94 | });
95 | });
96 | describe('with a specific version from the CDN', function() {
97 | const expected = 'https://js.arcgis.com/3.42/';
98 | let scriptEl;
99 | beforeAll(function(done) {
100 | fakeLoading();
101 | loadScript({
102 | version: '3.42'
103 | })
104 | .then((script) => {
105 | // hold onto script element for assertions below
106 | scriptEl = script;
107 | done();
108 | });
109 | });
110 | it('should load CDN version', function() {
111 | expect(scriptEl.src).toEqual(expected);
112 | });
113 | });
114 | describe('with a specific url', function() {
115 | const url = 'http://server/path/to/esri';
116 | let scriptEl;
117 | beforeAll(function(done) {
118 | fakeLoading();
119 | loadScript({
120 | url
121 | })
122 | .then((script) => {
123 | // hold onto script element for assertions below
124 | scriptEl = script;
125 | done();
126 | });
127 | });
128 | it('should load url', function() {
129 | expect(scriptEl.src).toEqual(url);
130 | });
131 | });
132 | describe('with css option', function() {
133 | describe('from default version', () => {
134 | beforeAll(function(done) {
135 | fakeLoading();
136 | loadScript({
137 | css: true
138 | })
139 | .then((script) => {
140 | done();
141 | });
142 | });
143 | it('should have called loadCss with no arguments', function() {
144 | expect((cssUtils.loadCss as jasmine.Spy).calls.argsFor(0)[0]).toBeUndefined();
145 | });
146 | });
147 | describe('with a specific version from the CDN', () => {
148 | const version = '3.42';
149 | beforeAll(function(done) {
150 | fakeLoading();
151 | loadScript({
152 | version,
153 | css: true
154 | })
155 | .then((script) => {
156 | done();
157 | });
158 | });
159 | it('should have called loadCss with the version', function() {
160 | expect((cssUtils.loadCss as jasmine.Spy).calls.argsFor(0)[0]).toEqual(version);
161 | });
162 | });
163 | describe('with a specific url', () => {
164 | const url = 'http://server/path/to/esri';
165 | const cssUrl = `${url}/css/main.css`;
166 | beforeAll(function(done) {
167 | fakeLoading();
168 | loadScript({
169 | url,
170 | css: cssUrl
171 | })
172 | .then((script) => {
173 | done();
174 | });
175 | });
176 | it('should have called loadCss with the url', function() {
177 | expect((cssUtils.loadCss as jasmine.Spy).calls.argsFor(0)[0]).toEqual(cssUrl);
178 | });
179 | });
180 | });
181 | describe('when already loaded by some other means', function() {
182 | beforeAll(function() {
183 | stubRequire();
184 | });
185 | it('should reject', function(done) {
186 | loadScript({
187 | url: jaspi3xUrl
188 | })
189 | .then(() => {
190 | done.fail('call to loadScript should have failed');
191 | })
192 | .catch((err) => {
193 | expect(err.message).toEqual(`The ArcGIS API for JavaScript is already loaded.`);
194 | done();
195 | });
196 | });
197 | afterAll(function() {
198 | // clean up
199 | removeRequire();
200 | });
201 | });
202 | describe('when loading an invalid url', function() {
203 | it('should pass an error to the callback', function(done) {
204 | loadScript({
205 | url: 'not a valid url'
206 | })
207 | .then(() => {
208 | done.fail('call to loadScript should have failed');
209 | })
210 | .catch((err) => {
211 | expect(err.message.indexOf('There was an error attempting to load')).toEqual(0);
212 | done();
213 | });
214 | });
215 | afterAll(function() {
216 | // clean up
217 | removeScript();
218 | });
219 | });
220 | describe('when called twice', function() {
221 | describe('when loading the same script', function() {
222 | it('should resolve the script if it is already loaded', function(done) {
223 | loadScript({
224 | url: jaspi3xUrl
225 | })
226 | .then(() => {
227 | // try loading the same script after the first one has already loaded
228 | loadScript({
229 | url: jaspi3xUrl
230 | })
231 | .then((script) => {
232 | expect(script.getAttribute('src')).toEqual(jaspi3xUrl);
233 | done();
234 | })
235 | .catch((err) => {
236 | done.fail('second call to loadScript should not have failed with: ' + err);
237 | });
238 | })
239 | .catch(() => {
240 | done.fail('first call to loadScript should not have failed');
241 | });
242 | });
243 | it('should resolve an unloaded script once it loads', function(done) {
244 | loadScript({
245 | url: jaspi3xUrl
246 | })
247 | .catch(() => {
248 | done.fail('first call to loadScript should not have failed');
249 | });
250 | // try loading the same script again
251 | loadScript({
252 | url: jaspi3xUrl
253 | })
254 | .then((script) => {
255 | expect(script.getAttribute('src')).toEqual(jaspi3xUrl);
256 | done();
257 | })
258 | .catch((err) => {
259 | done.fail('second call to loadScript should not have failed with: ' + err);
260 | });
261 | });
262 | });
263 | describe('when loading different scripts', function() {
264 | it('should reject', function(done) {
265 | loadScript({
266 | url: jaspi3xUrl
267 | })
268 | .catch(() => {
269 | done.fail('first call to loadScript should not have failed');
270 | });
271 | // try loading a different script
272 | loadScript({
273 | url: 'base/test/mocks/jsapi4x.js'
274 | })
275 | .then(() => {
276 | done.fail('second call to loadScript should have failed');
277 | })
278 | .catch((err) => {
279 | expect(err.message).toEqual(`The ArcGIS API for JavaScript is already loaded (${jaspi3xUrl}).`);
280 | done();
281 | });
282 | });
283 | });
284 | afterEach(function() {
285 | // clean up
286 | removeRequire();
287 | removeScript();
288 | });
289 | });
290 | });
291 |
--------------------------------------------------------------------------------
/src/script.ts:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2022 Environmental Systems Research Institute, Inc.
2 | * Apache-2.0 */
3 |
4 | import { loadCss } from './utils/css';
5 | import utils from './utils/index';
6 | import { getCdnUrl } from './utils/url';
7 |
8 | let defaultOptions: ILoadScriptOptions = {};
9 |
10 | function createScript(url) {
11 | const script = document.createElement('script');
12 | script.type = 'text/javascript';
13 | script.src = url;
14 | script.setAttribute('data-esri-loader', 'loading');
15 | return script;
16 | }
17 |
18 | // add a one-time load handler to script
19 | // and optionally add a one time error handler as well
20 | function handleScriptLoad(script, callback, errback?) {
21 | let onScriptError;
22 | if (errback) {
23 | // set up an error handler as well
24 | onScriptError = handleScriptError(script, errback);
25 | }
26 | const onScriptLoad = () => {
27 | // pass the script to the callback
28 | callback(script);
29 | // remove this event listener
30 | script.removeEventListener('load', onScriptLoad, false);
31 | if (onScriptError) {
32 | // remove the error listener as well
33 | script.removeEventListener('error', onScriptError, false);
34 | }
35 | };
36 | script.addEventListener('load', onScriptLoad, false);
37 | }
38 |
39 | // add a one-time error handler to the script
40 | function handleScriptError(script, callback) {
41 | const onScriptError = (e) => {
42 | // reject the promise and remove this event listener
43 | callback(e.error || new Error(`There was an error attempting to load ${script.src}`));
44 | // remove this event listener
45 | script.removeEventListener('error', onScriptError, false);
46 | };
47 | script.addEventListener('error', onScriptError, false);
48 | return onScriptError;
49 | }
50 |
51 | // interfaces
52 | export interface ILoadScriptOptions {
53 | version?: string;
54 | url?: string;
55 | css?: string | boolean;
56 | // NOTE: in the future we could support adding the data-dojo-config attribute via
57 | // dojoConfig?: string;
58 | insertCssBefore?: string;
59 | }
60 |
61 | // allow the user to configure default script options rather than passing options to `loadModules` each time
62 | export function setDefaultOptions(options: ILoadScriptOptions = {}): void {
63 | defaultOptions = options;
64 | }
65 |
66 | // get the script injected by this library
67 | export function getScript() {
68 | return document.querySelector('script[data-esri-loader]') as HTMLScriptElement;
69 | }
70 | // has ArcGIS API been loaded on the page yet?
71 | export function isLoaded() {
72 | const globalRequire = window['require'];
73 | // .on() ensures that it's Dojo's AMD loader
74 | return globalRequire && globalRequire.on;
75 | }
76 |
77 | // load the ArcGIS API on the page
78 | export function loadScript(options: ILoadScriptOptions = {}): Promise {
79 | // we would have liked to use spread like { ...defaultOptions, ...options }
80 | // but TS would inject a polyfill that would require use to configure rollup w content: 'window'
81 | // if we have another occasion to use spread, let's do that and replace this for...in
82 | const opts: ILoadScriptOptions = {};
83 | [defaultOptions, options].forEach((obj) => {
84 | for (const prop in obj) {
85 | if (Object.prototype.hasOwnProperty.call(obj, prop)) {
86 | opts[prop] = obj[prop];
87 | }
88 | }
89 | });
90 | // URL to load
91 | const version = opts.version;
92 | const url = opts.url || getCdnUrl(version);
93 |
94 | return new utils.Promise((resolve, reject) => {
95 | let script = getScript();
96 | if (script) {
97 | // the API is already loaded or in the process of loading...
98 | // NOTE: have to test against scr attribute value, not script.src
99 | // b/c the latter will return the full url for relative paths
100 | const src = script.getAttribute('src');
101 | if (src !== url) {
102 | // potentially trying to load a different version of the API
103 | reject(new Error(`The ArcGIS API for JavaScript is already loaded (${src}).`));
104 | } else {
105 | if (isLoaded()) {
106 | // the script has already successfully loaded
107 | resolve(script);
108 | } else {
109 | // wait for the script to load and then resolve
110 | handleScriptLoad(script, resolve, reject);
111 | }
112 | }
113 | } else {
114 | if (isLoaded()) {
115 | // the API has been loaded by some other means
116 | // potentially trying to load a different version of the API
117 | reject(new Error(`The ArcGIS API for JavaScript is already loaded.`));
118 | } else {
119 | // this is the first time attempting to load the API
120 | const css = opts.css;
121 | if (css) {
122 | const useVersion = css === true;
123 | // load the css before loading the script
124 | loadCss(useVersion ? version : (css as string), opts.insertCssBefore);
125 | }
126 | // create a script object whose source points to the API
127 | script = createScript(url);
128 | // _currentUrl = url;
129 | // once the script is loaded...
130 | handleScriptLoad(script, () => {
131 | // update the status of the script
132 | script.setAttribute('data-esri-loader', 'loaded');
133 | // return the script
134 | resolve(script);
135 | }, reject);
136 | // load the script
137 | document.body.appendChild(script);
138 | }
139 | }
140 | });
141 | }
142 |
--------------------------------------------------------------------------------
/src/utils/css.test.ts:
--------------------------------------------------------------------------------
1 | import { loadCss } from './css';
2 |
3 | describe('when loading the css', () => {
4 | describe('with no arguments', () => {
5 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css';
6 | let link;
7 | beforeAll(() => {
8 | spyOn(document.head, 'appendChild').and.stub();
9 | spyOn(document, 'querySelector');
10 | link = loadCss();
11 | });
12 | it('should have checked if the link was already appended', () => {
13 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`);
14 | });
15 | it('should have set the href', () => {
16 | expect(link.href).toEqual(url);
17 | });
18 | it('should not have set the rel', () => {
19 | expect(link.rel).toEqual('stylesheet');
20 | });
21 | });
22 | describe('with a version', () => {
23 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css';
24 | let link;
25 | beforeAll(() => {
26 | spyOn(document.head, 'appendChild').and.stub();
27 | spyOn(document, 'querySelector');
28 | link = loadCss('4.25');
29 | });
30 | it('should have checked if the link was already appended', () => {
31 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`);
32 | });
33 | it('should have set the href', () => {
34 | expect(link.href).toEqual(url);
35 | });
36 | it('should not have set the rel', () => {
37 | expect(link.rel).toEqual('stylesheet');
38 | });
39 | });
40 | describe('with "next"', () => {
41 | const url = 'https://js.arcgis.com/next/esri/themes/light/main.css';
42 | let link;
43 | beforeAll(() => {
44 | spyOn(document.head, 'appendChild').and.stub();
45 | spyOn(document, 'querySelector');
46 | link = loadCss('next');
47 | });
48 | it('should have checked if the link was already appended', () => {
49 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`);
50 | });
51 | it('should have set the href', () => {
52 | expect(link.href).toEqual(url);
53 | });
54 | it('should not have set the rel', () => {
55 | expect(link.rel).toEqual('stylesheet');
56 | });
57 | });
58 | describe('with a url', () => {
59 | const url = 'http://server/path/to/esri/themes/light/main.css';
60 | let link;
61 | beforeAll(() => {
62 | spyOn(document.head, 'appendChild').and.stub();
63 | spyOn(document, 'querySelector');
64 | link = loadCss(url);
65 | });
66 | it('should have checked if the link was already appended', () => {
67 | expect((document.querySelector as jasmine.Spy).calls.argsFor(0)[0]).toEqual(`link[href*="${url}"]`);
68 | });
69 | it('should have set the href', () => {
70 | expect(link.href).toEqual(url);
71 | });
72 | it('should not have set the rel', () => {
73 | expect(link.rel).toEqual('stylesheet');
74 | });
75 | });
76 | describe('when called twice', () => {
77 | describe('when loading the same url', () => {
78 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css';
79 | let link;
80 | let link2;
81 | beforeAll(() => {
82 | spyOn(document.head, 'appendChild').and.stub();
83 | link = loadCss(url);
84 | spyOn(document, 'querySelector').and.returnValue(link);
85 | link2 = loadCss(url);
86 | });
87 | it('should return the link if it is already loaded', () => {
88 | expect(link2).toEqual(link);
89 | });
90 | it('should not have tried to append the link a second time', () => {
91 | expect((document.head.appendChild as jasmine.Spy).calls.count()).toEqual(1);
92 | });
93 | });
94 | });
95 | describe('when inserting before an existing node', () => {
96 | const url = 'https://js.arcgis.com/4.25/esri/themes/light/main.css';
97 | // insert before the first