perf(post): lazy-link maplibre's stylesheet instead of bundling it
The engine was already a lazy import(), but its CSS was a static one — the usual workaround for esbuild never emitting a <link> for a code-split chunk's stylesheet (R10). That folded the whole vendor sheet into post-form.css, so every /post load paid for a panel most submits never open, and ~78% of those bytes were rules for controls this map never creates (popups, geolocate, zoom, compass, fullscreen, terrain, scale). Build the vendor sheet as its own css-compiled/maplibre-gl.css and have location-map.js <link> it at panel-open, concurrently with the engine's import(). Keeping the file intact rather than hand-picking the ~16 selectors in use means a maplibre upgrade can't silently un-style the map. post-form.css 92,244 → 26,784 raw (14,528 → 5,631 gzip) The href resolves from import.meta.url, so it is correct under any Grav base path without threading a URL through the template — the panel is built entirely in JS, so there is no element to hang a data-attr on. A failed stylesheet load resolves rather than rejects: the map degrades to unstyled, never to absent. The existing lazy-load guard spec already matched every maplibre-gl URL, so it covers the stylesheet too; its message now says so, and a new test asserts the other half — that opening the panel does link and apply the sheet.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,7 +6,8 @@
|
||||
* needs (KTD1).
|
||||
*
|
||||
* maplibre-gl itself is lazy-imported on first call (KTD3) so an ordinary
|
||||
* GPS-only submit, where the panel is never opened, never fetches it. The
|
||||
* GPS-only submit, where the panel is never opened, never fetches it. Its
|
||||
* STYLESHEET is lazy too — see ensureMaplibreCss below. The
|
||||
* created map/marker are cached in module scope and reused on every
|
||||
* subsequent call — see the panel's toggle-open handler in post-form.js,
|
||||
* which calls this on every open and then always calls the returned
|
||||
@@ -22,7 +23,48 @@
|
||||
*/
|
||||
import { MAP_STYLE } from './map-style.js';
|
||||
|
||||
var cached = null; // { container, promise }
|
||||
var cached = null; // { container, promise }
|
||||
var cssPromise = null; // in-flight/settled <link> load for maplibre's stylesheet
|
||||
|
||||
/**
|
||||
* Inject maplibre-gl's stylesheet on demand, once.
|
||||
*
|
||||
* esbuild will not emit a <link> for a code-split chunk's CSS (R10), so the
|
||||
* usual fix is a static `import 'maplibre-gl/dist/maplibre-gl.css'` up in
|
||||
* post-form.js — but that defeats the lazy import it accompanies: every /post
|
||||
* load would then pay ~9 KB gzip of vendor CSS for a panel most submits never
|
||||
* open. So package.json builds the vendor stylesheet as its own
|
||||
* css-compiled/maplibre-gl.css and we <link> it here instead, in parallel with
|
||||
* the engine's import(). Keeping the vendor file intact (rather than
|
||||
* hand-picking the ~16 selectors this panel actually uses) means a maplibre
|
||||
* upgrade can't silently un-style the map.
|
||||
*
|
||||
* The href is resolved from import.meta.url — the bundle is ESM, so this is the
|
||||
* URL of this chunk under `js/post/`, which makes the link correct under any
|
||||
* Grav base path without threading a URL through the template. (The panel is
|
||||
* built entirely in JS, so there is no Twig element to hang a data-attr on.)
|
||||
*
|
||||
* Resolves on error as well as load: a missing stylesheet must degrade to an
|
||||
* unstyled-but-functional map, never block it. On error the dead <link> and the
|
||||
* memo are dropped so a later retry re-attempts it, mirroring the map cache's
|
||||
* clear-on-failure below.
|
||||
*/
|
||||
function ensureMaplibreCss() {
|
||||
if (cssPromise) return cssPromise;
|
||||
cssPromise = new Promise(function (resolve) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = new URL('../../css-compiled/maplibre-gl.css', import.meta.url).href;
|
||||
link.onload = function () { resolve(); };
|
||||
link.onerror = function () {
|
||||
link.remove();
|
||||
cssPromise = null;
|
||||
resolve();
|
||||
};
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
return cssPromise;
|
||||
}
|
||||
|
||||
function buildPinElement() {
|
||||
var el = document.createElement('div');
|
||||
@@ -35,7 +77,14 @@ export function getOrCreateLocationMap(container, onDragEnd) {
|
||||
return cached.promise;
|
||||
}
|
||||
|
||||
var promise = import('maplibre-gl').then(function (mod) {
|
||||
// Stylesheet and engine fetch concurrently; awaiting the CSS before
|
||||
// constructing the Map means maplibre never measures the container against
|
||||
// half-applied styles.
|
||||
var promise = Promise.all([
|
||||
import('maplibre-gl'),
|
||||
ensureMaplibreCss()
|
||||
]).then(function (loaded) {
|
||||
var mod = loaded[0];
|
||||
var maplibregl = mod.default || mod;
|
||||
var map = new maplibregl.Map({
|
||||
container: container,
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
import EasyMDE from 'easymde';
|
||||
import Sortable from 'sortablejs';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
// maplibre-gl's CSS is a static import — a dynamically-imported chunk's CSS is
|
||||
// never auto-linked (R10). The JS itself stays a lazy import (see location-map.js).
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
// NOTE: maplibre-gl's CSS is deliberately NOT imported here. esbuild never emits
|
||||
// a <link> for a dynamically-imported chunk's CSS (R10), and a static import
|
||||
// would have folded 68 KB / ~9 KB gzip of vendor stylesheet — ~78% of it rules
|
||||
// for controls this panel never creates — into post-form.css on every /post
|
||||
// load. location-map.js instead injects a <link> to the separately-built
|
||||
// css-compiled/maplibre-gl.css at the moment it lazy-imports the engine, so an
|
||||
// ordinary GPS-only submit pays nothing for either half.
|
||||
import './post-form.css';
|
||||
import { apiSend, apiErrorMsg } from './api-utils.js';
|
||||
import { getOrCreateLocationMap } from './location-map.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "node scripts/gen-weather-icons.js && esbuild js/src/main.js --bundle --minify --format=iife --outfile=js/main.js --loader:.woff2=file --loader:.woff=file --asset-names=../fonts/[name] && esbuild js/src/map.js --bundle --minify --format=iife --outfile=js/map.js && esbuild js/src/feed-actions.js --bundle --minify --format=iife --outfile=js/feed-actions.js && esbuild js/src/trip-publish.js --bundle --minify --format=iife --outfile=js/trip-publish.js && rm -rf js/post && esbuild js/src/post-form.js --bundle --minify --format=esm --splitting --outdir=js/post && mkdir -p css-compiled fonts && { mv js/main.css css-compiled/main.css 2>/dev/null || true; } && { mv js/map.css css-compiled/map.css 2>/dev/null || true; } && { mv js/post/post-form.css css-compiled/post-form.css 2>/dev/null || true; }"
|
||||
"build": "node scripts/gen-weather-icons.js && esbuild js/src/main.js --bundle --minify --format=iife --outfile=js/main.js --loader:.woff2=file --loader:.woff=file --asset-names=../fonts/[name] && esbuild js/src/map.js --bundle --minify --format=iife --outfile=js/map.js && esbuild js/src/feed-actions.js --bundle --minify --format=iife --outfile=js/feed-actions.js && esbuild js/src/trip-publish.js --bundle --minify --format=iife --outfile=js/trip-publish.js && rm -rf js/post && esbuild js/src/post-form.js --bundle --minify --format=esm --splitting --outdir=js/post && mkdir -p css-compiled fonts && esbuild node_modules/maplibre-gl/dist/maplibre-gl.css --bundle --minify --outfile=css-compiled/maplibre-gl.css && { mv js/main.css css-compiled/main.css 2>/dev/null || true; } && { mv js/map.css css-compiled/map.css 2>/dev/null || true; } && { mv js/post/post-form.css css-compiled/post-form.css 2>/dev/null || true; }"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/dm-sans": "latest",
|
||||
|
||||
Reference in New Issue
Block a user