Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat: add dev-only component #7950

Merged
merged 9 commits into from Oct 26, 2022
18 changes: 18 additions & 0 deletions docs/content/2.guide/2.directory-structure/1.components.md
Expand Up @@ -223,6 +223,24 @@ This feature only works with Nuxt auto-imports and `#components` imports. Explic
</template>
```

## `<DevOnly>` Component

Nuxt provides the `<DevOnly>` component to render a component only during development.

The content will not be included in production builds and tree-shaken.

```html{}[pages/example.vue]
<template>
<div>
<Sidebar />
<DevOnly>
<!-- this component will only be rendered during development -->
<LazyDebugBar />
</DevOnly>
</div>
</template>
```

## Library Authors

Making Vue component libraries with automatic tree-shaking and component registration is super easy ✨
Expand Down
11 changes: 11 additions & 0 deletions packages/nuxt/src/app/components/dev-only.mjs
@@ -0,0 +1,11 @@
import { defineComponent } from 'vue'

export default defineComponent({
name: 'DevOnly',
setup (_, props) {
if (process.dev) {
return () => props.slots.default?.()
}
return () => null
}
})
11 changes: 11 additions & 0 deletions packages/nuxt/src/core/nuxt.ts
Expand Up @@ -17,6 +17,7 @@ import { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import { UnctxTransformPlugin } from './plugins/unctx'
import { TreeShakePlugin } from './plugins/tree-shake'
import { DevOnlyPlugin } from './plugins/dev-only'
import { addModuleTranspiles } from './modules'
import { initNitro } from './nitro'

Expand Down Expand Up @@ -89,6 +90,10 @@ async function initNuxt (nuxt: Nuxt) {
addVitePlugin(TreeShakePlugin.vite({ sourcemap: nuxt.options.sourcemap.client, treeShake: removeFromClient }), { server: false })
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap.server, treeShake: removeFromServer }), { client: false })
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap.client, treeShake: removeFromClient }), { server: false })

// DevOnly component tree-shaking - build time only
addVitePlugin(DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
addWebpackPlugin(DevOnlyPlugin.webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
}

// TODO: [Experimental] Avoid emitting assets when flag is enabled
Expand Down Expand Up @@ -139,6 +144,12 @@ async function initNuxt (nuxt: Nuxt) {
filePath: resolve(nuxt.options.appDir, 'components/client-only')
})

// Add <DevOnly>
addComponent({
name: 'DevOnly',
filePath: resolve(nuxt.options.appDir, 'components/dev-only')
})

// Add <ServerPlaceholder>
addComponent({
name: 'ServerPlaceholder',
Expand Down
45 changes: 45 additions & 0 deletions packages/nuxt/src/core/plugins/dev-only.ts
@@ -0,0 +1,45 @@
import { pathToFileURL } from 'node:url'
import { stripLiteral } from 'strip-literal'
import { parseQuery, parseURL } from 'ufo'
import MagicString from 'magic-string'
import { createUnplugin } from 'unplugin'

interface DevOnlyPluginOptions {
sourcemap?: boolean
}

export const DevOnlyPlugin = createUnplugin((options: DevOnlyPluginOptions) => {
const DEVONLY_COMP_RE = /<dev-?only>(:?[\s\S]*)<\/dev-?only>/gmi

return {
name: 'nuxt:server-devonly:transfrom',
enforce: 'pre',
transformInclude (id) {
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
const { type } = parseQuery(search)

// vue files
if (pathname.endsWith('.vue') && (type === 'template' || !search)) {
return true
}
},
transform (code, id) {
if (!code.match(DEVONLY_COMP_RE)) { return }

const s = new MagicString(code)
const strippedCode = stripLiteral(code)
for (const match of strippedCode.matchAll(DEVONLY_COMP_RE) || []) {
s.remove(match.index!, match.index! + match[0].length)
}

if (s.hasChanged()) {
return {
code: s.toString(),
map: options.sourcemap
? s.generateMap({ source: id, includeContent: true })
: undefined
}
}
}
}
})