mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 07:32:01 +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 { version } from '../../package.json'
|
||||||
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
||||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
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 { DevOnlyPlugin } from './plugins/dev-only'
|
||||||
import { addModuleTranspiles } from './modules'
|
import { addModuleTranspiles } from './modules'
|
||||||
import { initNitro } from './nitro'
|
import { initNitro } from './nitro'
|
||||||
@ -79,22 +80,31 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
addVitePlugin(ImportProtectionPlugin.vite(config))
|
addVitePlugin(ImportProtectionPlugin.vite(config))
|
||||||
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
||||||
|
|
||||||
// Add unctx transform
|
|
||||||
nuxt.hook('modules:done', () => {
|
nuxt.hook('modules:done', () => {
|
||||||
|
// Add unctx transform
|
||||||
addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
|
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 }))
|
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) {
|
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
|
// DevOnly component tree-shaking - build time only
|
||||||
addVitePlugin(DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
|
addVitePlugin(DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }))
|
||||||
addWebpackPlugin(DevOnlyPlugin.webpack({ 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 MagicString from 'magic-string'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
|
|
||||||
interface TreeShakePluginOptions {
|
type ImportPath = string
|
||||||
|
|
||||||
|
export interface TreeShakeComposablesPluginOptions {
|
||||||
sourcemap?: boolean
|
sourcemap?: boolean
|
||||||
treeShake: string[]
|
composables: Record<ImportPath, string[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TreeShakePlugin = createUnplugin((options: TreeShakePluginOptions) => {
|
export const TreeShakeComposablesPlugin = createUnplugin((options: TreeShakeComposablesPluginOptions) => {
|
||||||
const COMPOSABLE_RE = new RegExp(`($\\s+)(${options.treeShake.join('|')})(?=\\()`, 'gm')
|
/**
|
||||||
|
* @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 {
|
return {
|
||||||
name: 'nuxt:server-treeshake:transform',
|
name: 'nuxt:tree-shake-composables:transform',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
transformInclude (id) {
|
transformInclude (id) {
|
||||||
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||||
|
@ -51,7 +51,7 @@ export default defineUntypedSchema({
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```js
|
* ```js
|
||||||
transpile: [({ isLegacy }) => isLegacy && 'ky']
|
transpile: [({ isLegacy }) => isLegacy && 'ky']
|
||||||
* ```
|
* ```
|
||||||
* @type {Array<string | RegExp | ((ctx: { isClient?: boolean; isServer?: boolean; isDev: boolean }) => string | RegExp | false)>}
|
* @type {Array<string | RegExp | ((ctx: { isClient?: boolean; isServer?: boolean; isDev: boolean }) => string | RegExp | false)>}
|
||||||
*/
|
*/
|
||||||
@ -81,7 +81,7 @@ export default defineUntypedSchema({
|
|||||||
*/
|
*/
|
||||||
templates: [],
|
templates: [],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nuxt uses `webpack-bundle-analyzer` to visualize your bundles and how to optimize them.
|
* 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).
|
* 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}
|
* @type {boolean | typeof import('webpack-bundle-analyzer').BundleAnalyzerPlugin.Options | typeof import('rollup-plugin-visualizer').PluginVisualizerOptions}
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
analyze: {
|
analyze: {
|
||||||
$resolve: async (val, get) => {
|
$resolve: async (val, get) => {
|
||||||
if (val !== true) {
|
if (val !== true) {
|
||||||
return val ?? false
|
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', () => {
|
describe('server tree shaking', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const html = await $fetch('/client')
|
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')
|
const internalParent = pages.find(page => page.path === '/internal-layout')
|
||||||
internalParent!.children = newPages
|
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: {
|
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