diff --git a/docs/getting-started.md b/docs/getting-started.md index da7230fd..468f2644 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -28,13 +28,7 @@ The map component also requires a **map provider** — a separate library that h ### MapLibre provider (recommended) -**ESM:** `maplibre-gl` is a peer dependency, install it separately: - -```shell -npm install maplibre-gl -``` - -**UMD:** `maplibre-gl` is bundled — no separate install needed. +`maplibre-gl` is installed with this package (ESM) or bundled (UMD) — no separate install needed. ### ESRI provider (optional) @@ -54,6 +48,69 @@ npm install ol proj4 **UMD:** `ol` and `proj4` are bundled — no separate install needed. +### Bundler configuration (ESM) + +Some plugins work with more than one map provider. They load an adapter for your provider at runtime, and each adapter imports its own map engine. Only the adapter for your provider is ever loaded in the browser, but your bundler still builds every adapter, so it fails to resolve the engines you haven't installed: + +``` +Module not found: Error: Can't resolve 'ol/layer/Vector.js' +``` + +Tell your bundler to ignore the engines for the providers you don't use. Plugins that need this say so at the top of their documentation. + +| Provider | Ignore | Pattern | +|---|---|---| +| MapLibre | `ol`, `proj4`, `@arcgis/core` | `/^(ol\|proj4\|@arcgis\/core)(\/\|$)/` | +| OpenLayers | `@arcgis/core` | `/^@arcgis\/core(\/\|$)/` | +| ESRI | `ol`, `proj4` | `/^(ol\|proj4)(\/\|$)/` | + +The examples below are for the MapLibre provider. For another provider, swap in its pattern. + +**Webpack** + +```js +import webpack from 'webpack' + +export default { + // ... + plugins: [ + new webpack.IgnorePlugin({ resourceRegExp: /^(ol|proj4|@arcgis\/core)(\/|$)/ }) + ] +} +``` + +**Vite** (production builds; the dev server only warns) + +```js +export default { + build: { + rollupOptions: { // rolldownOptions in Vite 8+ + external: [/^(ol|proj4|@arcgis\/core)(\/|$)/] + } + } +} +``` + +**Rollup** + +```js +export default { + // ... + external: [/^(ol|proj4|@arcgis\/core)(\/|$)/] +} +``` + +**esbuild** + +```js +await esbuild.build({ + // ... + external: ['ol', 'ol/*', 'proj4', '@arcgis/core', '@arcgis/core/*'] +}) +``` + +The ignored engines are only used by adapters for other providers, which are never loaded, so nothing is missing at runtime. If you switch provider, update the pattern to match. + ## Basic usage **ESM** — add a container element to your HTML and initialise the map in your JavaScript: diff --git a/docs/plugins/datasets.md b/docs/plugins/datasets.md index 213b17a1..bc8382dc 100644 --- a/docs/plugins/datasets.md +++ b/docs/plugins/datasets.md @@ -5,6 +5,9 @@ The datasets plugin renders GeoJSON and vector tile datasets on the map, with su > [!IMPORTANT] > **Upgrading?** This plugin no longer renders key of symbols itself — that button and panel have been removed. Add the [Map Key](./map-key.md) plugin to restore it. Your `showInKey` config is unaffected and needs no changes. +> [!IMPORTANT] +> **Using a bundler (ESM)?** This plugin includes adapters for more than one map provider, so your bundler needs to ignore the map engines you haven't installed. See [Bundler configuration](../getting-started.md#bundler-configuration-esm). + ## ESM usage ```js diff --git a/docs/plugins/draw.md b/docs/plugins/draw.md index 015c4a36..beb9de9e 100644 --- a/docs/plugins/draw.md +++ b/docs/plugins/draw.md @@ -1,6 +1,9 @@ # Draw Plugin -The draw plugin lets users draw and edit point, polygon and line features on the map — placing vertices by click, tap, or keyboard, snapping to existing map layers, and validating geometry as it's built. Polygons can also be split and merged. It works identically with both the MapLibre and OpenLayers map providers, determining the correct adapter to use from the `mapProvider` passed to `InteractiveMap` — there's nothing to configure. +The draw plugin lets users draw and edit point, polygon and line features on the map — placing vertices by click, tap, or keyboard, snapping to existing map layers, and validating geometry as it's built. Polygons can also be split and merged. It works identically with both the MapLibre and OpenLayers map providers, determining the correct adapter to use from the `mapProvider` passed to `InteractiveMap` — there's nothing to configure in the plugin itself. + +> [!IMPORTANT] +> **Using a bundler (ESM)?** This plugin includes adapters for more than one map provider, so your bundler needs to ignore the map engines you haven't installed. See [Bundler configuration](../getting-started.md#bundler-configuration-esm). ## ESM usage diff --git a/rollup.esm.mjs b/rollup.esm.mjs index 238b6310..cd1ac867 100644 --- a/rollup.esm.mjs +++ b/rollup.esm.mjs @@ -82,7 +82,7 @@ const PREACT_EXTERNALS = [ // dependency so it auto-installs for consumers and is resolved transparently. const BABEL_RUNTIME_EXTERNAL = /@babel\/runtime/ -const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null, extraExternals = []) => { +const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null, chunkNames = {}) => { const esmDir = path.resolve(__dirname, outDir) // Use the parent dir as output.dir so CSS can be emitted to css/index.css // (a sibling subdir) without Rollup 4's ban on ".." in emitted file names. @@ -104,12 +104,13 @@ const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null, : [ ...PREACT_EXTERNALS, BABEL_RUNTIME_EXTERNAL, - // maplibre-gl is externalised so ESM consumers get a single shared - // instance from their own node_modules rather than a 1 MB copy bundled - // into the provider. (UMD keeps it bundled — no bundler available there.) + // Map engines (optional peers) are externalised so consumers share one copy + // from their own node_modules; they ignore the engines they don't install + // (see docs/getting-started.md). UMD keeps them bundled. 'maplibre-gl', - /^@arcgis\/core/, - ...extraExternals + /^ol\//, + 'proj4', + /^@arcgis\/core/ ], plugins: [ @@ -196,7 +197,7 @@ const createESMConfig = (entryPath, outDir, isCore = false, manualChunks = null, // - anything else → im-shell.js (the sync InteractiveMap + shared utils chunk) chunkFileNames: isCore ? (chunk) => chunk.name === 'initialiseApp' ? 'esm/im-core.js' : 'esm/im-shell.js' - : 'esm/[name].js', + : (chunk) => `esm/${chunkNames[chunk.name] || chunk.name}.js`, // Rollup ignores webpack magic comments; manualChunks is how we assign // meaningful names to lazy-loaded splits. // Core: no manualChunks — Rollup's natural algorithm keeps shared source @@ -234,7 +235,6 @@ const ALL_BUILDS = [ { entryPath: './providers/beta/openlayers/src/index.js', outDir: 'providers/beta/openlayers/dist/esm', - extraExternals: [/^ol\//, 'proj4'], manualChunks: (id) => id.includes('/openlayersProvider') ? 'im-openlayers-provider' : undefined }, @@ -262,13 +262,9 @@ const ALL_BUILDS = [ { entryPath: './plugins/datasets/src/index.js', outDir: 'plugins/datasets/dist/esm', - manualChunks: (id) => { - if (id.includes('/manifest')) { return 'im-datasets-plugin' } - if (id.includes('maplibreLayerAdapter')) { return 'im-datasets-ml-adapter' } - if (id.includes('openlayersLayerAdapter')) { return 'im-datasets-ol-adapter' } - if (id.includes('esriLayerAdapter')) { return 'im-datasets-esri-adapter' } - return undefined - } + // Adapters renamed rather than in manualChunks — see the draw build below + manualChunks: (id) => id.includes('/manifest') ? 'im-datasets-plugin' : undefined, + chunkNames: { maplibreLayerAdapter: 'im-datasets-ml-adapter', openlayersLayerAdapter: 'im-datasets-ol-adapter', esriLayerAdapter: 'im-datasets-esri-adapter' } }, { entryPath: './plugins/beta/map-styles/src/index.js', @@ -278,13 +274,12 @@ const ALL_BUILDS = [ { entryPath: './plugins/draw/src/index.js', outDir: 'plugins/draw/dist/esm', - extraExternals: [/^ol\//], - manualChunks: (id) => { - if (id.includes('/manifest')) { return 'im-draw-plugin' } - if (id.includes('MaplibreDrawAdapter')) { return 'im-draw-ml-adapter' } - if (id.includes('OLDrawAdapter')) { return 'im-draw-ol-adapter' } - return undefined - } + // Adapters are renamed rather than put in manualChunks: manual chunks absorb their + // dependencies, which put helpers shared by both adapters inside the MapLibre one, so + // OpenLayers hosts downloaded the whole MapLibre adapter. Left to Rollup, shared + // helpers get a small chunk of their own. + manualChunks: (id) => id.includes('/manifest') ? 'im-draw-plugin' : undefined, + chunkNames: { MaplibreDrawAdapter: 'im-draw-ml-adapter', OLDrawAdapter: 'im-draw-ol-adapter' } }, { entryPath: './plugins/beta/draw-ml/src/index.js', @@ -299,7 +294,6 @@ const ALL_BUILDS = [ { entryPath: './plugins/beta/draw-ol/src/index.js', outDir: 'plugins/beta/draw-ol/dist/esm', - extraExternals: [/^ol\//], manualChunks: (id) => id.includes('/manifest') ? 'im-draw-ol-plugin' : undefined }, { @@ -326,5 +320,5 @@ const buildsToRun = BUILD_TARGET : ALL_BUILDS export default buildsToRun.map(b => - createESMConfig(b.entryPath, b.outDir, b.isCore || false, b.manualChunks || null, b.extraExternals || []) + createESMConfig(b.entryPath, b.outDir, b.isCore || false, b.manualChunks || null, b.chunkNames) ) diff --git a/webpack.umd.mjs b/webpack.umd.mjs index a4b89b57..66ebe60f 100755 --- a/webpack.umd.mjs +++ b/webpack.umd.mjs @@ -2,6 +2,7 @@ import path, { dirname } from 'path' import { fileURLToPath } from 'url' import fs from 'fs' +import webpack from 'webpack' import MiniCssExtractPlugin from 'mini-css-extract-plugin' import CssMinimizerPlugin from 'css-minimizer-webpack-plugin' import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts' @@ -30,7 +31,10 @@ const createUMDConfig = (entryName, entryPath, libraryPath, outDir, isCore = fal }), new RemoveFilesPlugin({ before: { include: [path.resolve(__dirname, outDir)] } - }) + }), + // There's no Esri provider in UMD, so Esri code can never run here: leave out + // @arcgis/core and the datasets Esri adapter rather than bundling ~100 MB of chunks. + new webpack.IgnorePlugin({ resourceRegExp: /^@arcgis\/core|esriLayerAdapter/ }) ] if (isCore) {