mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): tree-shake client and server-only composables (#5749)
This commit is contained in:
parent
5c323ca195
commit
4d607080f5
@ -59,6 +59,7 @@
|
||||
"pathe": "^0.3.2",
|
||||
"perfect-debounce": "^0.1.3",
|
||||
"scule": "^0.2.1",
|
||||
"strip-literal": "^0.4.0",
|
||||
"ufo": "^0.8.4",
|
||||
"unctx": "^1.1.4",
|
||||
"unenv": "^0.5.2",
|
||||
|
@ -14,6 +14,7 @@ import { distDir, pkgDir } from '../dirs'
|
||||
import { version } from '../../package.json'
|
||||
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||
import { TreeShakePlugin } from './plugins/tree-shake'
|
||||
import { addModuleTranspiles } from './modules'
|
||||
import { initNitro } from './nitro'
|
||||
|
||||
@ -67,6 +68,17 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap }))
|
||||
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap }))
|
||||
|
||||
if (!nuxt.options.dev) {
|
||||
const removeFromServer = ['onBeforeMount', 'onMounted', 'onBeforeUpdate', 'onRenderTracked', 'onRenderTriggered', 'onActivated', 'onDeactivated', 'onBeforeUnmount']
|
||||
const removeFromClient = ['onServerPrefetch', 'onRenderTracked', 'onRenderTriggered']
|
||||
|
||||
// Add tree-shaking optimisations for SSR - build time only
|
||||
addVitePlugin(TreeShakePlugin.vite({ sourcemap: nuxt.options.sourcemap, treeShake: removeFromServer }), { client: false })
|
||||
addVitePlugin(TreeShakePlugin.vite({ sourcemap: nuxt.options.sourcemap, treeShake: removeFromClient }), { server: false })
|
||||
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap, treeShake: removeFromServer }), { client: false })
|
||||
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap, treeShake: removeFromClient }), { server: false })
|
||||
}
|
||||
|
||||
// Transpile layers within node_modules
|
||||
nuxt.options.build.transpile.push(
|
||||
...nuxt.options._layers.filter(i => i.cwd && i.cwd.includes('node_modules')).map(i => i.cwd)
|
||||
|
49
packages/nuxt/src/core/plugins/tree-shake.ts
Normal file
49
packages/nuxt/src/core/plugins/tree-shake.ts
Normal file
@ -0,0 +1,49 @@
|
||||
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 TreeShakePluginOptions {
|
||||
sourcemap?: boolean
|
||||
treeShake: string[]
|
||||
}
|
||||
|
||||
export const TreeShakePlugin = createUnplugin((options: TreeShakePluginOptions) => {
|
||||
const COMPOSABLE_RE = new RegExp(`($|\\s*)(${options.treeShake.join('|')})(?=\\()`, 'g')
|
||||
|
||||
return {
|
||||
name: 'nuxt:server-treeshake:transfrom',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||
const { type, macro } = parseQuery(search)
|
||||
|
||||
// vue files
|
||||
if (pathname.endsWith('.vue') && (type === 'script' || macro || !search)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// js files
|
||||
if (pathname.match(/\.((c|m)?j|t)sx?$/g)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
transform (code, id) {
|
||||
if (!code.match(COMPOSABLE_RE)) { return }
|
||||
|
||||
const s = new MagicString(code)
|
||||
const strippedCode = stripLiteral(code)
|
||||
for (const match of strippedCode.matchAll(COMPOSABLE_RE) || []) {
|
||||
s.overwrite(match.index, match.index + match[0].length, `(() => {}) || /*#__PURE__*/ false && ${match[0]}`)
|
||||
}
|
||||
|
||||
if (s.hasChanged()) {
|
||||
return {
|
||||
code: s.toString(),
|
||||
map: options.sourcemap && s.generateMap({ source: id, includeContent: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -244,6 +244,14 @@ describe('reactivity transform', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('server tree shaking', () => {
|
||||
it('should work', async () => {
|
||||
const html = await $fetch('/client')
|
||||
|
||||
expect(html).toContain('This page should not crash when rendered')
|
||||
})
|
||||
})
|
||||
|
||||
describe('extends support', () => {
|
||||
describe('layouts & pages', () => {
|
||||
it('extends foo/layouts/default & foo/pages/index', async () => {
|
||||
|
6
test/fixtures/basic/components/BreaksServer.ts
vendored
Normal file
6
test/fixtures/basic/components/BreaksServer.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// @ts-ignore
|
||||
window.test = true
|
||||
|
||||
export default () => ({
|
||||
render: () => 'hi'
|
||||
})
|
16
test/fixtures/basic/pages/client.vue
vendored
Normal file
16
test/fixtures/basic/pages/client.vue
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
onMounted(() => import('~/components/BreaksServer'))
|
||||
onBeforeMount(() => import('~/components/BreaksServer'))
|
||||
onBeforeUpdate(() => import('~/components/BreaksServer'))
|
||||
onRenderTracked(() => import('~/components/BreaksServer'))
|
||||
onRenderTriggered(() => import('~/components/BreaksServer'))
|
||||
onActivated(() => import('~/components/BreaksServer'))
|
||||
onDeactivated(() => import('~/components/BreaksServer'))
|
||||
onBeforeUnmount(() => import('~/components/BreaksServer'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
This page should not crash when rendered.
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user