├── .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 | [![deprecated](http://badges.github.io/stability-badges/dist/deprecated.svg)](http://github.com/badges/stability-badges) [![npm](https://img.shields.io/npm/v/esri-loader.svg)](https://github.com/Esri/esri-loader/releases) [![npm](https://img.shields.io/npm/dw/esri-loader.svg)](https://www.npmjs.com/package/esri-loader) [![npm](https://img.shields.io/npm/l/esri-loader.svg)](https://github.com/Esri/esri-loader/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/esri/esri-loader.svg?style=social&label=Stars)](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