Skip to content

Commit

Permalink
feat(nuxt): tree-shake client and server-only composables (nuxt#5749)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Jul 7, 2022
1 parent 5c323ca commit 4d60708
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)
Expand Down
49 changes: 49 additions & 0 deletions packages/nuxt/src/core/plugins/tree-shake.ts
Original file line number Diff line number Diff line change
@@ -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 })
}
}
}
}
})
8 changes: 8 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/basic/components/BreaksServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @ts-ignore
window.test = true

export default () => ({
render: () => 'hi'
})
16 changes: 16 additions & 0 deletions test/fixtures/basic/pages/client.vue
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9900,6 +9900,7 @@ __metadata:
pathe: ^0.3.2
perfect-debounce: ^0.1.3
scule: ^0.2.1
strip-literal: ^0.4.0
ufo: ^0.8.4
unbuild: latest
unctx: ^1.1.4
Expand Down

0 comments on commit 4d60708

Please sign in to comment.