Build ArcGIS API for JavaScript applications with webpack
- Features
- Usage
- Options
- Best Practices
- Sample Applications
- How does it work?
- Things we're working on
- Issues
- Contributing
- Licensing
This plugin utilizes the dojo-webpack-plugin to build ArcGIS API for JavaScript applications with webpack.
Requires version 4.7.0
or greater of the ArcGIS API for JavaScript
- Uses a lightweight AMD loader during the Webpack build process to resolve module paths. The AMD loader is not used in the output application.
- Creates bundles of your application with ArcGIS API for JavaScript included.
npm install --save-dev @arcgis/webpack-plugin
In order for the workers used in the ArcGIS API for JavaScript to work correctly, you will need to provide an external loader.
This is a temporary solution until we can update how workers are loaded in the ArcGIS API for JavaScript so they are compatible with the output of a webpack build.
import esriConfig from "esri/config";
const DEFAULT_WORKER_URL = "https://js.arcgis.com/4.12/";
const DEFAULT_LOADER_URL = `${DEFAULT_WORKER_URL}dojo/dojo-lite.js`;
esriConfig.workers.loaderUrl = DEFAULT_LOADER_URL;
esriConfig.workers.loaderConfig = {
baseUrl: `${DEFAULT_WORKER_URL}dojo`,
packages: [
{ name: "esri", location: `${DEFAULT_WORKER_URL}esri` },
{ name: "dojo", location: `${DEFAULT_WORKER_URL}dojo` },
{ name: "dojox", location: `${DEFAULT_WORKER_URL}dojox` },
{ name: "dstore", location: `${DEFAULT_WORKER_URL}dstore` },
{ name: "moment", location: `${DEFAULT_WORKER_URL}moment` },
{ name: "@dojo", location: `${DEFAULT_WORKER_URL}@dojo` },
{
name: "cldrjs",
location: `${DEFAULT_WORKER_URL}cldrjs`,
main: "dist/cldr"
},
{
name: "globalize",
location: `${DEFAULT_WORKER_URL}globalize`,
main: "dist/globalize"
},
{
name: "maquette",
location: `${DEFAULT_WORKER_URL}maquette`,
main: "dist/maquette.umd"
},
{
name: "maquette-css-transitions",
location: `${DEFAULT_WORKER_URL}maquette-css-transitions`,
main: "dist/maquette-css-transitions.umd"
},
{
name: "maquette-jsx",
location: `${DEFAULT_WORKER_URL}maquette-jsx`,
main: "dist/maquette-jsx.umd"
},
{ name: "tslib", location: `${DEFAULT_WORKER_URL}tslib`, main: "tslib" }
]
};
Options | Default | Description |
---|---|---|
useDefaultAssetLoaders |
true |
By default, this plugin provides url-loader for images and file-loader for fonts and svg that are used by the ArcGIS API for JavaScript. If you are using another library that requires you to also load assets, you may want to disable the default loaders of this plugin and use your own. |
root |
"." |
Is used in the env passed to your loader configuration. See environment details in the dojo-webpack-plugin. |
locales |
undefined |
The locales you want included in your build output. See the locales details of the dojo-webpack-plugin. |
features |
{} | ADVANCED - See the Additional Features section |
userDefinedExcludes |
[] |
ADVANCED - You can provide an array modules as string that you want to exclude from the output bundles. For example, you may want to exclude layers you are not using. |
options |
undefined |
ADVANCED - You can pass any native options of the dojo-webpack-plugin if you want to override some of the defaults of this plugin. This would also allow you to use your own loaderConfig instead of the default one. |
The bare minimum to start using the plugin is the following:
// webpack.config.js
const ArcGISPlugin = require("@arcgis/webpack-plugin");
// add it to config
module.exports = {
...
plugins: [new ArcGISPlugin()]
...
}
If you notice some oddities in the path resolutions of modules in your bundles, you can try to define how you want them referenced.
plugins: [
new ArcGISPlugin({
// "../app" or similar depending on your build.
// most likely do not need to change
root: ".",
// If you specify locales in the build
// only those locales are available ar runtime.
// Leave undefined and all locales will be available at runtime.
locales: ["en"]
})
];
Maybe you want to override all the default options of this plugin.
plugins: [
new ArcGISPlugin({
options: {
loaderConfig: require("./js/loaderConfig"),
environment: { appRoot: "release" },
buildEnvironment: { appRoot: "node_modules" },
locales: ["en"]
}
})
];
Whether you are using a TypeScript loader like ts-loader or writing modern JavaScript and using the babel-loader, the output files from the loader need to be in AMD. This is so the dependencies can be picked up by the lightweight AMD loader of this plugin during build time.
That means that for TypeScript, your tsconfig.json
should have the following option:
{
"compilerOptions": {
"module": "amd"
}
}
For babel, update your configuration as follow :
{
"presets": [["@babel/preset-env",{"modules":"amd"}]]
}
Webpack will include ES6 Promises in your bundles, so you may want to include a Promise shim in your application. Some more detail here. We have tested the @dojo/shim/Promise
with success in our test applications.
See the dojo-webpack-plugin-sample for more details.
See options section for details of options you can provide to the plugin.
It is recommended that you ignore the node process
and global
, so they don't get built into your bundle. You want to set the fs
module to empty
so that the webassembly files of the client-side projection engine are loaded correctly.
// webpack.config.js
node: {
process: false,
global: false,
fs: "empty"
}
When working with CSS, you can load the files directly from your application and let the html-webpack-plugin
and mini-css-extract-plugin
output the CSS file and inject the file location directly into your output HTML file.
Note: mini-css-extract-plugin requires webpack 4 to work. If you are using webpack 3, you can use the extract-text-webpack-plugin
.
import "./css/main.scss";
// webpack.config.js
...
module: {
rules: [
...
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: { minimize: false }
}
],
exclude: /node_modules/
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
}
]
},
plugins: [
...
// will copy your index.html file
// and inject assets for you
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html",
chunksSortMode: "none"
}),
// will output a css file that you
// imported in your application
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
...
However, if you would like to use the style-loader
to load your styles at runtime, you will need to modify your code.
import "css!./css/main.scss";
// webpack.config.js
...
plugins: [
...
// For plugins registered after the ArcGISPlugin, data.request has been normalized and
// resolved to an absMid and other mappings and aliases have been applied.
// You will need to update the loader plugin appropriately.
new webpack.NormalModuleReplacementPlugin(/^css!/, function(data) {
data.request = data.request.replace(
/^css!/,
"!style-loader!css-loader!sass-loader?indentedSyntax=false"
);
})
...
Please note, we have tested the @arcgis/webpack-plugin
with numerous other plugins, but cannot guarantee that other webpack plugins may not cause some unexpected behavior.
By default, this plugin provides provides url-loader for images and file-loader for assets that are only used by the ArcGIS API for JavaScript. However, if you are using another library that you need to load image, svg, or fonts for, you will want to provide your own loaders. You will want to set the useDefaultAssetLoaders
to false
.
// webpack.config.js
...
plugins: [
new ArcGISPlugin({
// disable provided asset loaders
useDefaultAssetLoaders: false
})
],
...
Then you can provide your own asset loaders.
// webpack.config.js
...
module: {
rules: [
...
{
test: /\.(jpe?g|png|gif|webp)$/,
loader: "url-loader",
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
}
},
{
test: /\.(wsv|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [
{
loader: "file-loader",
options: {
name: "build/[name].[ext]"
}
}
]
}
]
}
...
NOTE - Advanced Usage
If you are building a 2D mapping application and do not require 3D, you can exclude 3D related modules by disabling the 3d features
. This option will remove 3D modules from the output JavaScript bundles for your application. Please note, this does not impact the file size of the JavaScript used in your application, only in the number of bundles generated.
// webpack.config.js
...
plugins: [
new ArcGISPlugin({
// exclude 3D modules from build
features: {
"3d": false
}
})
],
...
You also have the option to pass in an array of other modules that you may want to exclude from your application. For example, maybe you are not using a particular set of layers. You can add them to the userDefinedExcludes
option.
// webpack.config.js
...
plugins: [
new ArcGISPlugin({
// exclude modules you do not need
userDefinedExcludes: [
"arcgis-js-api/layers/BingMapsLayer",
"arcgis-js-api/layers/CSVLayer",
"arcgis-js-api/layers/GeoRSSLayer",
"arcgis-js-api/layers/ImageryLayer",
"arcgis-js-api/layers/KMLLayer",
"arcgis-js-api/layers/MapImageLayer",
"arcgis-js-api/layers/OpenStreetMapLayer",
"arcgis-js-api/layers/StreamLayer",
"arcgis-js-api/layers/WMSLayer",
"arcgis-js-api/layers/WMTSLayer",
"arcgis-js-api/layers/WebTileLayer"
]
})
],
...
Again, this considered ADVANCED usage, so please use with caution.
You can provide addition has
flags to the build as well.
// webpack.config.js
...
plugins: [
new ArcGISPlugin({
features: {
has: {
// enable native promise in ArcGIS API for JavaScript
'esri-native-promise': true,
}
}
})
],
...
Here are some example applications that you can try out for yourself:
- TypeScript: https://github.com/Esri/jsapi-resources/tree/master/4.x/webpack/demo
- Babel: https://github.com/odoe/jsapi-webpack
This plugin utilizes the dojo-webpack-plugin
and provides some default settings out-of-the-box.
For the following, you can see the source code for details of how the plugin is put together.
There are some files that need to be copied over after the bundles are done so they can be referenced dynamically at runtime. We manage this as part of the plugin.
const requiredPlugins = [
// Copy non-packed resources needed by the app to the build directory
new CopyWebpackPlugin([
{
context: "node_modules",
from: "dojo/resources/blank.gif",
to: "dojo/resources"
},
{
context: "node_modules",
from: "@arcgis/webpack-plugin/extras/dojo/",
to: "dojo/"
},
{
context: "node_modules",
from: "@arcgis/webpack-plugin/extras/dojo/dojo.js",
to: "dojo/dojo-lite.js"
},
{
context: "node_modules",
from: "arcgis-js-api/core/request/iframe.html",
to: "arcgis-js-api/core/request/iframe.html"
},
{
context: "node_modules",
from: "arcgis-js-api/views/3d/environment/resources/stars.wsv",
to: "arcgis-js-api/views/3d/environment/resources/stars.wsv"
},
{
context: "node_modules",
from: "arcgis-js-api/geometry/support/pe-wasm.wasm",
to: "arcgis-js-api/geometry/support/pe-wasm.wasm"
},
{
context: "node_modules",
from: "arcgis-js-api/themes/base/images/",
to: "arcgis-js-api/themes/base/images/"
},
{
context: "node_modules",
from: "arcgis-js-api/images/",
to: "arcgis-js-api/images/"
},
{
context: "node_modules",
from: "arcgis-js-api/workers/",
to: "arcgis-js-api/workers/"
},
{
context: "node_modules",
from: "arcgis-js-api/core/workers/",
to: "arcgis-js-api/core/workers/"
}
]),
...
];
This plugin also utilizes the webpack-hasjs-plugin to set compile time static features that help enable the removal of unused code.
const requiredPlugins = [
...
// Check for has() features in the build
// Feature list taken directly from what is
// used in Dojo builds
new HasJsPlugin({
features: {
"some-static-feature": false
}
}),
...
];
There are cases in the ArcGIS API for JavaScript where feature detection is used.
if (has("some-static-feature")) {
return true;
}
else {
return false;
}
This will be converted to the following.
if (false) {
return true;
}
else {
return false;
}
So when the code is run through the build and minification becomes only return false
.
Please refer to the webpack-hasjs-plugin for known limitations.
We are also able to change a couple of references to loader plugins and replace the output with defined modules. We manage that as part of the plugin as well.
const requiredPlugins = [
...
// For plugins registered after the DojoAMDPlugin, data.request has been normalized and
// resolved to an absMid and loader-config maps and aliases have been applied
new webpack.NormalModuleReplacementPlugin(
/^dojox\/gfx\/renderer!/,
"dojox/gfx/svg"
),
new webpack.NormalModuleReplacementPlugin(/\/moment!/, "moment/moment")
];
We have some additional loaders we add out-of-the-box that are utilized as part of the Webpack bundling of CSS files and other assets.
const additionalLoaders = [
// @dojo modules are referenced in the API and need to be loaded via a umd loader
{
test: /@dojo/,
use: "umd-compat-loader"
},
{
// scoped to the arcgis-js-api resources only
test: /arcgis-js-api\/.*(jpe?g|png|gif|webp)$/,
loader: "url-loader",
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
}
},
{
// scoped to the arcgis-js-api resources only
test: /arcgis-js-api\/.*(wsv|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [
{
loader: "file-loader",
options: {
name: "build/[name].[ext]"
}
}
]
}
];
Then we instantiate the dojo-webpack-plugin with the options provided and pass that into the Webpack pipeline.
this.options = {
globalContext: path.join(__dirname, "node_modules", "arcgis-js-api"),
environment: {
root: options.root || "."
},
buildEnvironment: {
root: "node_modules"
}
};
this.options = { ...this.options, ...options.options };
if (!this.options.loaderConfig) {
this.options.loaderConfig = require("./lib/loaderConfig");
}
this.dojoPlugin = new DojoWebpackPlugin(this.options);
We also provide a default loaderConfig for this plugin that defines the location of the ArcGIS API for JavaScript and its dependencies. Note, this should only be used for dependencies of the ArcGIS API for JavaScript, you do not need to define locations for other libraries used with your application.
We provide some default has
configurations of the loader.
...
has: {
"dojo-config-api": 0, // Don't need the config API code in the embedded Dojo loader
"esri-promise-compatibility": 1, // Use native Promises by default
"esri-webpack": 1, // a flag used internally by the ArcGIS API for JavaScript
"esri-featurelayer-webgl": 1 // Enable FeatureLayer WebGL capabilities
}
...
- Compatibility with
DllPlugin
. We're trying to find how we can create a single Dll or multiple Dll files to share across multiple applications. - Improved bundles. We're going to be working towards trying to reduce the number of bundles that Webpack generates due to how we dynamically import modules at runtime.
Find a bug or want to request a new feature enhancement? Let us know by submitting an issue.
Anyone and everyone is welcome to contribute. We do accept pull requests.
- Get involved
- Report issues
- Contribute code
- Improve documentation
Copyright 2018 Esri
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
A copy of the license is available in the repository's LICENSE file