From 409482d5db74231e0f84f6c1dd621bd324c55c61 Mon Sep 17 00:00:00 2001 From: codecalm Date: Sun, 4 Oct 2026 23:29:57 +0200 Subject: [PATCH] Ship MapView as the tabler-map.js add-on bundle - tabler.js is back to its size before the maps --- .agents/skills/core-js/SKILL.md | 7 +++-- .build/vite.config.helper.mts | 10 +++++-- core/.build/vite.config.mts | 6 +++- core/js/tabler-map.ts | 6 ++++ core/js/tabler.ts | 2 -- core/libs.json | 1 + core/package.json | 30 +++++++++++++++---- core/tsconfig.types.json | 2 +- docs/content/ui/components/map.mdx | 13 ++++++-- docs/layouts/DocsLayout.astro | 4 ++- screenshots/layouts/ScreenshotAppLayout.astro | 4 ++- screenshots/layouts/ScreenshotLayout.astro | 4 ++- shared/layouts/BaseLayout.astro | 5 +++- 13 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 core/js/tabler-map.ts diff --git a/.agents/skills/core-js/SKILL.md b/.agents/skills/core-js/SKILL.md index 3b25525bf..8cfd097e7 100644 --- a/.agents/skills/core-js/SKILL.md +++ b/.agents/skills/core-js/SKILL.md @@ -7,14 +7,17 @@ description: >- # The framework JavaScript -`core/js` builds two shipped bundles. Everything here is public API and is bound by a size budget. +`core/js` builds three shipped bundles. Everything here is public API and is bound by a size budget. -## 1. Two entry points +## 1. Three entry points | Entry | Ships as | Holds | | -------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | | `js/tabler.ts` | `tabler.js` / `.esm.js` (+ `.min`) | plugin initialisers, the Bootstrap components, the `tabler` helper namespace | | `js/tabler-theme.ts` | `tabler-theme.js` (+ variants) | the colour-mode/theme switcher only | +| `js/tabler-map.ts` | `tabler-map.js` (+ variants) | the `MapView` component, as an add-on that extends the `tabler` global | + +`tabler-map.js` is loaded after `tabler.js`, and only on pages with a map: a component that few pages use goes in an add-on bundle like this one, not in `tabler.js`. Its UMD build uses `extend` (`core/.build/vite.config.mts`), so it adds its exports to the existing `tabler` global. `tabler-theme.js` is loaded right after `` and **not deferred**, so the chosen theme applies before the first paint. It stays tiny on purpose (bundlewatch: 1 kB raw, 800 B minified) — do not add anything to it that is not needed before paint. diff --git a/.build/vite.config.helper.mts b/.build/vite.config.helper.mts index f51464e79..769cd73b2 100644 --- a/.build/vite.config.helper.mts +++ b/.build/vite.config.helper.mts @@ -9,20 +9,26 @@ interface CreateViteConfigOptions { outDir: string banner?: string minify?: boolean | 'esbuild' + /** UMD only: add the exports to an existing global of the same name instead of replacing it */ + extend?: boolean } /** * Creates a Vite configuration for building libraries */ -export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false }: CreateViteConfigOptions): UserConfig { +export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false, extend = false }: CreateViteConfigOptions): UserConfig { // Vite 8 (Rolldown) always emits const bindings and no longer accepts the // Rollup-only generatedCode.constBindings option - const rollupOutput: { banner?: string } = {} + const rollupOutput: { banner?: string; extend?: boolean } = {} if (banner) { rollupOutput.banner = banner } + if (extend) { + rollupOutput.extend = true + } + const config: UserConfig = { // Library builds: never copy /public into outDir (see copy-assets.ts). publicDir: false, diff --git a/core/.build/vite.config.mts b/core/.build/vite.config.mts index f11c645bd..848cdfbf9 100644 --- a/core/.build/vite.config.mts +++ b/core/.build/vite.config.mts @@ -8,7 +8,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) const baseName = process.env.BASE_NAME || 'tabler' const entryFile = baseName -const libraryName = baseName +// `tabler-map` is an add-on to the main bundle: its UMD build extends the +// `tabler` global instead of creating one of its own. +const isAddon = baseName === 'tabler-map' +const libraryName = isAddon ? 'tabler' : baseName const bannerText = getBanner() @@ -26,4 +29,5 @@ export default createViteConfig({ outDir: path.resolve(__dirname, '../dist/js'), banner: bannerText, minify: false, + extend: isAddon, }) diff --git a/core/js/tabler-map.ts b/core/js/tabler-map.ts new file mode 100644 index 000000000..9afaad9a7 --- /dev/null +++ b/core/js/tabler-map.ts @@ -0,0 +1,6 @@ +// The map component ships as its own bundle, so pages without a map do not +// carry it. Load it after `tabler.js`: the UMD build adds `MapView` to the +// `tabler` global. +import './src/map' + +export { default as MapView } from './src/map' diff --git a/core/js/tabler.ts b/core/js/tabler.ts index 048b0dae7..ef8eadf58 100644 --- a/core/js/tabler.ts +++ b/core/js/tabler.ts @@ -4,7 +4,6 @@ import './src/confetti' import './src/countup' import './src/datepicker' import './src/input-mask' -import './src/map' import './src/dropdown' import './src/sidebar' import './src/tooltip' @@ -27,7 +26,6 @@ export { default as Confetti } from './src/confetti' export { default as CountUp } from './src/countup' export { default as Datepicker } from './src/datepicker' export { default as InputMask } from './src/input-mask' -export { default as MapView } from './src/map' export { default as OtpInput } from './src/otp-input' export { default as Sortable } from './src/sortable' export { default as Sparkline } from './src/sparkline' diff --git a/core/libs.json b/core/libs.json index 5d0e423d9..53f65c597 100644 --- a/core/libs.json +++ b/core/libs.json @@ -58,6 +58,7 @@ "maplibre-gl": { "npm": "maplibre-gl", "module": { "file": "dist/maplibre-gl.mjs", "global": "maplibregl" }, + "addon": "tabler-map", "css": ["dist/maplibre-gl.css"], "extra": ["dist/maplibre-gl.mjs", "dist/maplibre-gl-shared.mjs", "dist/maplibre-gl-worker.mjs"] }, diff --git a/core/package.json b/core/package.json index 8033c064a..b01592812 100644 --- a/core/package.json +++ b/core/package.json @@ -17,12 +17,14 @@ "lint:scss:vars": "find-unused-sass-variables scss/ --ignoreFiles scss/_variables-deprecated.scss", "js": "pnpm run js:build && pnpm run js:min && pnpm run js:types", "js:types": "tsc -p tsconfig.types.json", - "js:build": "concurrently \"pnpm run js:build:standalone\" \"pnpm run js:build:theme\"", + "js:build": "concurrently \"pnpm run js:build:standalone\" \"pnpm run js:build:theme\" \"pnpm run js:build:map\"", "js:build:theme": "cross-env BASE_NAME=tabler-theme vite build --config .build/vite.config.mts", + "js:build:map": "cross-env BASE_NAME=tabler-map vite build --config .build/vite.config.mts", "js:build:standalone": "cross-env BASE_NAME=tabler vite build --config .build/vite.config.mts", - "js:min": "concurrently \"pnpm run js:min:standalone\" \"pnpm run js:min:theme\"", + "js:min": "concurrently \"pnpm run js:min:standalone\" \"pnpm run js:min:theme\" \"pnpm run js:min:map\"", "js:min:standalone": "concurrently \"terser dist/js/tabler.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler.js.map,filename=dist/js/tabler.min.js.map,url=tabler.min.js.map\\\" -o dist/js/tabler.min.js\" \"terser dist/js/tabler.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler.esm.js.map,filename=dist/js/tabler.esm.min.js.map,url=tabler.esm.min.js.map\\\" -o dist/js/tabler.esm.min.js\"", "js:min:theme": "concurrently \"terser dist/js/tabler-theme.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-theme.js.map,filename=dist/js/tabler-theme.min.js.map,url=tabler-theme.min.js.map\\\" -o dist/js/tabler-theme.min.js\" \"terser dist/js/tabler-theme.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-theme.esm.js.map,filename=dist/js/tabler-theme.esm.min.js.map,url=tabler-theme.esm.min.js.map\\\" -o dist/js/tabler-theme.esm.min.js\"", + "js:min:map": "concurrently \"terser dist/js/tabler-map.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-map.js.map,filename=dist/js/tabler-map.min.js.map,url=tabler-map.min.js.map\\\" -o dist/js/tabler-map.min.js\" \"terser dist/js/tabler-map.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-map.esm.js.map,filename=dist/js/tabler-map.esm.min.js.map,url=tabler-map.esm.min.js.map\\\" -o dist/js/tabler-map.esm.min.js\"", "copy": "concurrently \"pnpm run copy:img\" \"pnpm run copy:libs\"", "copy:img": "tsx .build/copy-img.ts", "copy:libs": "tsx .build/copy-libs.ts", @@ -212,19 +214,35 @@ }, { "path": "./dist/js/tabler.js", - "maxSize": "61 kB" + "maxSize": "59 kB" }, { "path": "./dist/js/tabler.min.js", - "maxSize": "41 kB" + "maxSize": "40 kB" }, { "path": "./dist/js/tabler.esm.js", - "maxSize": "61 kB" + "maxSize": "59 kB" }, { "path": "./dist/js/tabler.esm.min.js", - "maxSize": "41 kB" + "maxSize": "40 kB" + }, + { + "path": "./dist/js/tabler-map.js", + "maxSize": "10 kB" + }, + { + "path": "./dist/js/tabler-map.esm.js", + "maxSize": "10 kB" + }, + { + "path": "./dist/js/tabler-map.min.js", + "maxSize": "7 kB" + }, + { + "path": "./dist/js/tabler-map.esm.min.js", + "maxSize": "7 kB" }, { "path": "./dist/js/tabler-theme.js", diff --git a/core/tsconfig.types.json b/core/tsconfig.types.json index b5e5e1e66..d4251d062 100644 --- a/core/tsconfig.types.json +++ b/core/tsconfig.types.json @@ -8,5 +8,5 @@ "rootDir": "js", "outDir": "dist/types" }, - "include": ["js/tabler.ts", "js/tabler-theme.ts", "js/src/**/*"] + "include": ["js/tabler.ts", "js/tabler-theme.ts", "js/tabler-map.ts", "js/src/**/*"] } diff --git a/docs/content/ui/components/map.mdx b/docs/content/ui/components/map.mdx index c51c3803e..d117509a8 100644 --- a/docs/content/ui/components/map.mdx +++ b/docs/content/ui/components/map.mdx @@ -61,12 +61,15 @@ MapLibre GL JS is published as an ES module. Tabler uses version 6. Load the sty // The map component looks for the library here window.maplibregl = maplibregl -`} + +`} /> +The map component is not part of `tabler.js`. It ships as a small add-on, `tabler-map.js`, so pages without a map do not carry it. Load it after `tabler.js`; it adds `MapView` to the `tabler` object. + The library loads two more files from the same folder, `maplibre-gl-shared.mjs` and `maplibre-gl-worker.mjs`, so keep the three together when you host them yourself. -With a bundler, install the package and set the global before you import Tabler: +With a bundler, install the package, set the global, and then import the add-on: ```bash npm install maplibre-gl @@ -77,8 +80,12 @@ import * as maplibregl from 'maplibre-gl' import 'maplibre-gl/dist/maplibre-gl.css' window.maplibregl = maplibregl + +const { MapView } = await import('@tabler/core/dist/js/tabler-map.esm.js') ``` +The add-on is imported after the global is set, because it turns the map elements on the page into maps as soon as it loads. + Older versions, including all of version 5, have a known security issue in the code that cleans the attribution text. @@ -316,7 +323,7 @@ Live versions of a route, an area, clusters, a bubble map, a globe with arcs and ### Via data attributes -An element with `data-bs-toggle="map"` becomes a map when the Tabler script loads: +An element with `data-bs-toggle="map"` becomes a map when the `tabler-map.js` script loads: diff --git a/docs/layouts/DocsLayout.astro b/docs/layouts/DocsLayout.astro index 40f403349..1c32849c1 100644 --- a/docs/layouts/DocsLayout.astro +++ b/docs/layouts/DocsLayout.astro @@ -142,7 +142,7 @@ const editFilePath = // docs-libs for this page. In dev, file names stay unchanged // (e.g. dist/plyr.min.js keeps .min). -type Lib = { npm?: string; js?: string[]; css?: string[]; module?: { file: string; global: string } }; +type Lib = { npm?: string; js?: string[]; css?: string[]; module?: { file: string; global: string }; addon?: string }; const libEntries = Object.entries(libs as Record); const libUrl = (lib: Lib, file: string) => isExternal(file) @@ -151,6 +151,7 @@ const libUrl = (lib: Lib, file: string) => const docsLibCss = libEntries .filter(([name]) => docsLibs.includes(name)) .flatMap(([, lib]) => (lib.css ?? []).map((file) => libUrl(lib, file))); +const docsLibAddons = libEntries.filter(([name]) => docsLibs.includes(name)).flatMap(([, lib]) => (lib.addon ? [lib.addon] : [])); const docsLibModules = libEntries .filter(([name]) => docsLibs.includes(name)) .flatMap(([, lib]) => (lib.module ? [`import * as lib from '${libUrl(lib, lib.module.file)}'; window.${lib.module.global} = lib`] : [])); @@ -525,6 +526,7 @@ const relatedPages = await Promise.all( + {docsLibAddons.map((addon) => `} />)} + {libAddons.map((addon) => `} />)} + {libAddons.map((addon) => + {libAddons.map((addon) =>