Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
]
},
{
Expand Down
81 changes: 81 additions & 0 deletions docs/guide/performance.md
Original file line number Diff line number Diff line change
@@ -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}
<template>
<div style="height:100vh; width:100vw">
<LMap :zoom="6" :center="[47.21322, -1.559482]">
<LTileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="&amp;copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a> contributors"
layer-type="base"
name="OpenStreetMap"
/>
</LMap>
</div>
</template>

<script setup lang="ts">
import 'leaflet/dist/leaflet.css'
</script>
```

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. |
28 changes: 22 additions & 6 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -40,13 +50,17 @@ export default defineNuxtModule<ModuleOptions>({
},
},
// 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) {
Expand All @@ -61,9 +75,11 @@ export default defineNuxtModule<ModuleOptions>({

// 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({
Expand Down
6 changes: 5 additions & 1 deletion test/basic.test.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -13,4 +13,8 @@ describe('nuxt leaflet', async () => {
// Verify there is no error
expect(html).toContain('<html')
})

it('adds Leaflet CSS to the global stylesheets by default', () => {
expect(useTestContext().nuxt?.options.css).toContain('leaflet/dist/leaflet.css')
})
})
23 changes: 23 additions & 0 deletions test/fixtures/no-inject-css/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<div style="height:100vh; width:100vw">
<h1>Map without the global CSS injection</h1>
<LMap
:zoom="6"
:max-zoom="18"
:center="[47.21322, -1.559482]"
>
<LTileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="&amp;copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a> contributors"
layer-type="base"
name="OpenStreetMap"
/>
</LMap>
</div>
</template>

<script setup lang="ts">
// The stylesheet is imported by the component itself, so it is only bundled
// in the chunks that actually need it.
import 'leaflet/dist/leaflet.css'
</script>
10 changes: 10 additions & 0 deletions test/fixtures/no-inject-css/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import NuxtLeaflet from '../../../src/module'

export default defineNuxtConfig({
modules: [NuxtLeaflet],
leaflet: {
injectCss: false,
},
ssr: false,
compatibilityDate: '2024-04-03',
})
5 changes: 5 additions & 0 deletions test/fixtures/no-inject-css/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"private": true,
"name": "no-inject-css",
"type": "module"
}
20 changes: 20 additions & 0 deletions test/no-inject-css.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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('<html')
})

it('does not add Leaflet CSS to the global stylesheets', () => {
expect(useTestContext().nuxt?.options.css).not.toContain('leaflet/dist/leaflet.css')
})
})