From 9e067df295c246d903c43fb73fd4f3b0c79939d2 Mon Sep 17 00:00:00 2001 From: Hossein Mirazimi Date: Mon, 31 Aug 2026 19:50:01 +0330 Subject: [PATCH 1/2] feat: add injectCss option to opt out of global CSS --- docs/.vitepress/config.ts | 3 +- docs/guide/performance.md | 81 ++++++++++++++++++++++ src/module.ts | 28 ++++++-- test/fixtures/no-inject-css/app.vue | 23 ++++++ test/fixtures/no-inject-css/nuxt.config.ts | 10 +++ test/fixtures/no-inject-css/package.json | 5 ++ test/no-inject-css.test.ts | 21 ++++++ 7 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 docs/guide/performance.md create mode 100644 test/fixtures/no-inject-css/app.vue create mode 100644 test/fixtures/no-inject-css/nuxt.config.ts create mode 100644 test/fixtures/no-inject-css/package.json create mode 100644 test/no-inject-css.test.ts diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f0eaf8c..9cc270f 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -28,7 +28,8 @@ export default defineConfig({ { text: 'Using L', link: '/guide/using-l' }, { text: 'Accessing a map instance', link: '/guide/accessing-map-instance' }, { text: 'Leaflet.markercluster', link: '/guide/marker-cluster' }, - { text: 'Leaflet.heat', link: '/guide/heat' } + { text: 'Leaflet.heat', link: '/guide/heat' }, + { text: 'Performance', link: '/guide/performance' } ] }, { diff --git a/docs/guide/performance.md b/docs/guide/performance.md new file mode 100644 index 0000000..75e2fe9 --- /dev/null +++ b/docs/guide/performance.md @@ -0,0 +1,81 @@ +--- +outline: deep +--- + +# Performance + +By default, the module adds Leaflet's stylesheet to `nuxt.options.css`: + +```ts +nuxt.options.css.push('leaflet/dist/leaflet.css') +``` + +This is the most convenient behaviour: the map is styled correctly everywhere, without +any extra work. But global CSS ends up in the entry stylesheet, which is render-blocking +on **every** route. In an application where maps only appear on one or two pages, all +the other pages still download and parse Leaflet's CSS before they can paint. + +## The `injectCss` option + +Set `injectCss` to `false` to opt out of the global injection: + +```ts{3-5} +export default defineNuxtConfig({ + modules: ['@nuxtjs/leaflet'], + leaflet: { + injectCss: false + } +}) +``` + +::: warning +When `injectCss` is `false`, the module no longer ships any stylesheet for you. You are +responsible for importing Leaflet's CSS wherever a map is rendered — otherwise the map +tiles, controls and popups will be laid out incorrectly. +::: + +Import the stylesheet in the components that actually render a map: + +```vue{12} + + + +``` + +Vite then bundles the stylesheet into the chunk of the route (or component) that imports +it, so it is only fetched by visitors who actually open a page with a map. Routes without +a map paint without waiting for Leaflet's CSS. + +If several components need it, you can also import it once in a shared component or in a +layout that is only used by the map pages. + +## Plugin stylesheets + +The option also applies to the stylesheets of the [Leaflet.markercluster](/guide/marker-cluster) +plugin. With `injectCss: false` and `markerCluster: true`, import them alongside Leaflet's +own stylesheet: + +```ts +import 'leaflet/dist/leaflet.css' +import 'leaflet.markercluster/dist/MarkerCluster.css' +import 'leaflet.markercluster/dist/MarkerCluster.Default.css' +``` + +## Options + +| Option | Type | Default | Description | +| ----------- | --------- | ------- | ----------------------------------------------------------------------------------------------- | +| `injectCss` | `boolean` | `true` | Add Leaflet's (and the enabled plugins') stylesheets to the global `css` array of your Nuxt app. | diff --git a/src/module.ts b/src/module.ts index 44258d7..d4c9559 100644 --- a/src/module.ts +++ b/src/module.ts @@ -4,6 +4,16 @@ import { defineNuxtModule, addComponent, createResolver, addImports } from '@nux export interface ModuleOptions { markerCluster?: boolean heat?: boolean + /** + * Inject Leaflet's stylesheets globally (in `nuxt.options.css`). + * + * Set to `false` to keep the map CSS out of the global entry stylesheet and + * import it yourself in the components using a map, so that it is only + * bundled in the chunks that actually need it. + * + * @default true + */ + injectCss?: boolean } // Components to export @@ -40,13 +50,17 @@ export default defineNuxtModule({ }, }, // Default configuration options of the Nuxt module - defaults: {}, + defaults: { + injectCss: true, + }, async setup(options, nuxt) { // Create a resolver for the module const resolver = createResolver(import.meta.url) - // Add Leaflet's CSS - nuxt.options.css.push('leaflet/dist/leaflet.css') + // Add Leaflet's CSS, unless the user opted out of the global injection + if (options.injectCss !== false) { + nuxt.options.css.push('leaflet/dist/leaflet.css') + } // Auto-import Vue Leaflet components for (const component of components) { @@ -61,9 +75,11 @@ export default defineNuxtModule({ // If leaflet.markercluster is enabled if (options.markerCluster) { - // Add Leaflet MarkerCluster CSS - nuxt.options.css.push('leaflet.markercluster/dist/MarkerCluster.css') - nuxt.options.css.push('leaflet.markercluster/dist/MarkerCluster.Default.css') + // Add Leaflet MarkerCluster CSS, unless the user opted out of the global injection + if (options.injectCss !== false) { + nuxt.options.css.push('leaflet.markercluster/dist/MarkerCluster.css') + nuxt.options.css.push('leaflet.markercluster/dist/MarkerCluster.Default.css') + } // Auto-import the runtime composable addImports({ diff --git a/test/fixtures/no-inject-css/app.vue b/test/fixtures/no-inject-css/app.vue new file mode 100644 index 0000000..11f951e --- /dev/null +++ b/test/fixtures/no-inject-css/app.vue @@ -0,0 +1,23 @@ + + + diff --git a/test/fixtures/no-inject-css/nuxt.config.ts b/test/fixtures/no-inject-css/nuxt.config.ts new file mode 100644 index 0000000..bb96881 --- /dev/null +++ b/test/fixtures/no-inject-css/nuxt.config.ts @@ -0,0 +1,10 @@ +import NuxtLeaflet from '../../../src/module' + +export default defineNuxtConfig({ + modules: [NuxtLeaflet], + leaflet: { + injectCss: false, + }, + ssr: false, + compatibilityDate: '2024-04-03', +}) diff --git a/test/fixtures/no-inject-css/package.json b/test/fixtures/no-inject-css/package.json new file mode 100644 index 0000000..bce8283 --- /dev/null +++ b/test/fixtures/no-inject-css/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "name": "no-inject-css", + "type": "module" +} diff --git a/test/no-inject-css.test.ts b/test/no-inject-css.test.ts new file mode 100644 index 0000000..2bd1e6e --- /dev/null +++ b/test/no-inject-css.test.ts @@ -0,0 +1,21 @@ +import { fileURLToPath } from 'node:url' +import { describe, it, expect } from 'vitest' +import { setup, $fetch, useTestContext } from '@nuxt/test-utils' + +describe('nuxt leaflet', async () => { + await setup({ + rootDir: fileURLToPath(new URL('./fixtures/no-inject-css', import.meta.url)), + }) + + it('renders a basic map without the global CSS injection', async () => { + // Get response to a server-rendered page with `$fetch`. + const html = await $fetch('/') + // Verify there is no error + expect(html).toContain(' { + const css = useTestContext().nuxt?.options.css ?? [] + expect(css).not.toContain('leaflet/dist/leaflet.css') + }) +}) From 463f6714d271f9665790da5677842fbcda94c960 Mon Sep 17 00:00:00 2001 From: Hossein Mirazimi Date: Mon, 31 Aug 2026 20:05:01 +0330 Subject: [PATCH 2/2] test: assert leaflet css injection follows injectCss --- test/basic.test.ts | 6 +++++- test/no-inject-css.test.ts | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/basic.test.ts b/test/basic.test.ts index 5802c20..e7d62fc 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url' import { describe, it, expect } from 'vitest' -import { setup, $fetch } from '@nuxt/test-utils' +import { setup, $fetch, useTestContext } from '@nuxt/test-utils' describe('nuxt leaflet', async () => { await setup({ @@ -13,4 +13,8 @@ describe('nuxt leaflet', async () => { // Verify there is no error expect(html).toContain(' { + expect(useTestContext().nuxt?.options.css).toContain('leaflet/dist/leaflet.css') + }) }) diff --git a/test/no-inject-css.test.ts b/test/no-inject-css.test.ts index 2bd1e6e..95cc7b5 100644 --- a/test/no-inject-css.test.ts +++ b/test/no-inject-css.test.ts @@ -15,7 +15,6 @@ describe('nuxt leaflet', async () => { }) it('does not add Leaflet CSS to the global stylesheets', () => { - const css = useTestContext().nuxt?.options.css ?? [] - expect(css).not.toContain('leaflet/dist/leaflet.css') + expect(useTestContext().nuxt?.options.css).not.toContain('leaflet/dist/leaflet.css') }) })