mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
feat(nuxt): allow configuring treeshakeable composables (#19383)
This commit is contained in:
parent
3a73f42d1c
commit
bb61496e98
@ -16,7 +16,8 @@ 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 type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake'
|
||||
import { TreeShakeComposablesPlugin } from './plugins/tree-shake'
|
||||
import { DevOnlyPlugin } from './plugins/dev-only'
|
||||
import { addModuleTranspiles } from './modules'
|
||||
import { initNitro } from './nitro'
|
||||
@ -79,22 +80,31 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
addVitePlugin(ImportProtectionPlugin.vite(config))
|
||||
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
||||
|
||||
// Add unctx transform
|
||||
nuxt.hook('modules:done', () => {
|
||||
// Add unctx transform
|
||||
addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
|
||||
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
|
||||
|
||||
// Add composable tree-shaking optimisations
|
||||
const serverTreeShakeOptions : TreeShakeComposablesPluginOptions = {
|
||||
sourcemap: nuxt.options.sourcemap.server,
|
||||
composables: nuxt.options.optimization.treeShake.composables.server
|
||||
}
|
||||
if (Object.keys(serverTreeShakeOptions.composables).length) {
|
||||
addVitePlugin(TreeShakeComposablesPlugin.vite(serverTreeShakeOptions), { client: false })
|
||||
addWebpackPlugin(TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false })
|
||||
}
|
||||
const clientTreeShakeOptions : TreeShakeComposablesPluginOptions = {
|
||||
sourcemap: nuxt.options.sourcemap.client,
|
||||
composables: nuxt.options.optimization.treeShake.composables.client
|
||||
}
|
||||
if (Object.keys(clientTreeShakeOptions.composables).length) {
|
||||
addVitePlugin(TreeShakeComposablesPlugin.vite(clientTreeShakeOptions), { server: false })
|
||||
addWebpackPlugin(TreeShakeComposablesPlugin.webpack(clientTreeShakeOptions), { server: false })
|
||||
}
|
||||
})
|
||||
|
||||
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.server, treeShake: removeFromServer }), { client: false })
|
||||
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 }))
|
||||
|
@ -4,16 +4,22 @@ import { parseQuery, parseURL } from 'ufo'
|
||||
import MagicString from 'magic-string'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
|
||||
interface TreeShakePluginOptions {
|
||||
type ImportPath = string
|
||||
|
||||
export interface TreeShakeComposablesPluginOptions {
|
||||
sourcemap?: boolean
|
||||
treeShake: string[]
|
||||
composables: Record<ImportPath, string[]>
|
||||
}
|
||||
|
||||
export const TreeShakePlugin = createUnplugin((options: TreeShakePluginOptions) => {
|
||||
const COMPOSABLE_RE = new RegExp(`($\\s+)(${options.treeShake.join('|')})(?=\\()`, 'gm')
|
||||
export const TreeShakeComposablesPlugin = createUnplugin((options: TreeShakeComposablesPluginOptions) => {
|
||||
/**
|
||||
* @todo Use the options import-path to tree-shake composables in a safer way.
|
||||
*/
|
||||
const composableNames = Object.values(options.composables).flat()
|
||||
const COMPOSABLE_RE = new RegExp(`($\\s+)(${composableNames.join('|')})(?=\\()`, 'gm')
|
||||
|
||||
return {
|
||||
name: 'nuxt:server-treeshake:transform',
|
||||
name: 'nuxt:tree-shake-composables:transform',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||
|
@ -51,7 +51,7 @@ export default defineUntypedSchema({
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
transpile: [({ isLegacy }) => isLegacy && 'ky']
|
||||
transpile: [({ isLegacy }) => isLegacy && 'ky']
|
||||
* ```
|
||||
* @type {Array<string | RegExp | ((ctx: { isClient?: boolean; isServer?: boolean; isDev: boolean }) => string | RegExp | false)>}
|
||||
*/
|
||||
@ -81,7 +81,7 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
templates: [],
|
||||
|
||||
/**
|
||||
/**
|
||||
* Nuxt uses `webpack-bundle-analyzer` to visualize your bundles and how to optimize them.
|
||||
*
|
||||
* Set to `true` to enable bundle analysis, or pass an object with options: [for webpack](https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin) or [for vite](https://github.com/btd/rollup-plugin-visualizer#options).
|
||||
@ -95,7 +95,7 @@ export default defineUntypedSchema({
|
||||
* @type {boolean | typeof import('webpack-bundle-analyzer').BundleAnalyzerPlugin.Options | typeof import('rollup-plugin-visualizer').PluginVisualizerOptions}
|
||||
*
|
||||
*/
|
||||
analyze: {
|
||||
analyze: {
|
||||
$resolve: async (val, get) => {
|
||||
if (val !== true) {
|
||||
return val ?? false
|
||||
@ -108,5 +108,40 @@ export default defineUntypedSchema({
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Build time optimization configuration.
|
||||
*/
|
||||
optimization: {
|
||||
/**
|
||||
* Tree shake code from specific builds.
|
||||
*/
|
||||
treeShake: {
|
||||
/**
|
||||
* Tree shake composables from the server or client builds.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* treeShake: { client: { myPackage: ['useServerOnlyComposable'] } }
|
||||
* ```
|
||||
*/
|
||||
composables: {
|
||||
server: {
|
||||
$resolve: async (val, get) => defu(val || {},
|
||||
await get('dev') ? {} : {
|
||||
vue: ['onBeforeMount', 'onMounted', 'onBeforeUpdate', 'onRenderTracked', 'onRenderTriggered', 'onActivated', 'onDeactivated', 'onBeforeUnmount'],
|
||||
}
|
||||
)
|
||||
},
|
||||
client: {
|
||||
$resolve: async (val, get) => defu(val || {},
|
||||
await get('dev') ? {} : {
|
||||
vue: ['onServerPrefetch', 'onRenderTracked', 'onRenderTriggered'],
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -560,6 +560,22 @@ describe('reactivity transform', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('composable tree shaking', () => {
|
||||
it('should work', async () => {
|
||||
const html = await $fetch('/tree-shake')
|
||||
|
||||
expect(html).toContain('Tree Shake Example')
|
||||
|
||||
const page = await createPage('/tree-shake')
|
||||
// check page doesn't have any errors or warnings in the console
|
||||
await page.waitForLoadState('networkidle')
|
||||
// ensure scoped classes are correctly assigned between client and server
|
||||
expect(await page.$eval('h1', e => getComputedStyle(e).color)).toBe('rgb(255, 192, 203)')
|
||||
|
||||
await expectNoClientErrors('/tree-shake')
|
||||
})
|
||||
})
|
||||
|
||||
describe('server tree shaking', () => {
|
||||
it('should work', async () => {
|
||||
const html = await $fetch('/client')
|
||||
|
16
test/fixtures/basic/composables/tree-shake.ts
vendored
Normal file
16
test/fixtures/basic/composables/tree-shake.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
export function useServerOnlyComposable () {
|
||||
if (process.client) {
|
||||
throw new Error('this should not be called in the browser')
|
||||
}
|
||||
}
|
||||
|
||||
export function useClientOnlyComposable () {
|
||||
// need to do some code that fails in node but not in the browser
|
||||
if (process.server) {
|
||||
throw new Error('this should not be called on the server')
|
||||
}
|
||||
}
|
||||
|
||||
export function setTitleToPink () {
|
||||
document.querySelector('h1')!.style.color = 'pink'
|
||||
}
|
4
test/fixtures/basic/nuxt.config.ts
vendored
4
test/fixtures/basic/nuxt.config.ts
vendored
@ -110,6 +110,10 @@ export default defineNuxtConfig({
|
||||
const internalParent = pages.find(page => page.path === '/internal-layout')
|
||||
internalParent!.children = newPages
|
||||
})
|
||||
},
|
||||
function (_, nuxt) {
|
||||
nuxt.options.optimization.treeShake.composables.server[nuxt.options.rootDir] = ['useClientOnlyComposable', 'setTitleToPink']
|
||||
nuxt.options.optimization.treeShake.composables.client[nuxt.options.rootDir] = ['useServerOnlyComposable']
|
||||
}
|
||||
],
|
||||
vite: {
|
||||
|
13
test/fixtures/basic/pages/tree-shake.vue
vendored
Normal file
13
test/fixtures/basic/pages/tree-shake.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
// server only
|
||||
useServerOnlyComposable()
|
||||
// client only
|
||||
useClientOnlyComposable()
|
||||
// can only run client side, should be tree shaken from server build
|
||||
setTitleToPink()
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h1>Tree Shake Example</h1>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user