Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/danielroe/nuxt-rebundle. Bundler optimisations for Nuxt
Something went wrong. Try again.
5.1 kB · 151 lines
TypeScript
at pnpm-setup
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152import type { ParsedStaticImport, StaticImport } from 'mlly'import type { SourceMapInput } from 'rollup'import type { Node } from 'estree-walker'
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'import { parse } from 'acorn'import { walk } from 'estree-walker'import MagicString from 'magic-string'import { findStaticImports, parseStaticImport } from 'mlly'import { dirname } from 'pathe'
const functionsToExtract = new Set(['useAsyncData', 'useLazyAsyncData'])const FUNCTIONS_RE = /\buse(?:Lazy)?AsyncData\b/
export interface ModuleOptions { enabled: boolean}
export default defineNuxtModule<ModuleOptions>({ meta: { configKey: 'boost', name: 'nuxt-rebundle', }, defaults: nuxt => ({ enabled: !nuxt.options.dev, }), setup(options) { if (!options.enabled) { return }
const asyncDatas: Record<string, { code: string, map: SourceMapInput }> = {}
addVitePlugin({ name: 'async-data-chunks', enforce: 'post', resolveId(source) { if (source in asyncDatas) { return source } }, load(id) { if (id in asyncDatas) { return asyncDatas[id] } }, transform(code, id) { if (id.includes('nuxt/dist/app') || !FUNCTIONS_RE.test(code)) { return }
let s: MagicString | undefined let imports: ParsedStaticImport[] | undefined
walk(parse(code, { sourceType: 'module', ecmaVersion: 'latest', locations: true, }) as Node, { enter(node) { // Looking for useAsyncData and useLazyAsyncData calls if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier' || !functionsToExtract.has(node.callee.name)) { return }
// Find the function passed as the second argument const fetcherFn = node.arguments[1] if (!fetcherFn || (fetcherFn.type !== 'ArrowFunctionExpression' && fetcherFn.type !== 'FunctionExpression')) { return }
s ||= new MagicString(code)
/** Extract variables in scope we need to include */ const variables = new Set<string>() const initialisers = new Set<string>()
walk(fetcherFn.body, { enter(node, parent, key) { if (node.type === 'Identifier' && key !== 'property' && key !== 'key') { variables.add(node.name) } else if (node.type === 'VariableDeclarator') { extractInitialisations(node.id, initialisers) } }, })
for (const r of initialisers) { variables.delete(r) } const importsToInsert: StaticImport[] = [] imports ||= findStaticImports(code).map(i => parseStaticImport(i))
for (const r of variables) { const i = imports.find(i => i.defaultImport === r || i.namespacedImport === r || Object.values(i.namedImports || {}).includes(r)) if (i) { variables.delete(r) importsToInsert.push(i) } }
const imps = importsToInsert.map(i => i.code).join('\n')
// preserve directory structure of purpose of resolving ids const key = `${dirname(id)}/async-data-chunk-${Math.random().toString(36).slice(2)}.js`
const childNode = fetcherFn.body.type === 'BlockStatement' ? fetcherFn.body.body : [fetcherFn.body] const getIndex = (loc: { line: number, column: number }) => code.split('\n').slice(0, loc.line - 1).join('\n').length + loc.column
const chunk = s.clone() const preface = `${imps}\nexport default async function (${[...variables].join(', ')}) { ${fetcherFn.body.type === 'BlockStatement' ? '' : `return `}` chunk.overwrite(0, getIndex(childNode[0]!.loc!.start), preface) chunk.overwrite(getIndex(childNode[childNode.length - 1]!.loc!.end) + 1, code.length, ` }`)
asyncDatas[key] = { code: chunk.toString(), map: chunk.generateMap({ hires: true }), }
s.overwrite(getIndex(fetcherFn.loc!.start), getIndex(fetcherFn.loc!.end) + 1, `() => import('${key}').then(r => (r.default || r)(${[...variables].join(', ')}))`) }, })
if (s?.hasChanged()) { return { code: s.toString(), map: s.generateMap({ hires: true }), } } }, }, { server: false }) },})
function extractInitialisations(node: Node, initialisers: Set<string>) { if (node.type === 'Identifier') { initialisers.add(node.name) } if (node.type === 'ObjectPattern') { node.properties.forEach((p) => { if (p.type === 'Property') { extractInitialisations(p.key, initialisers) } if (p.type === 'RestElement') { extractInitialisations(p.argument, initialisers) } }) } return initialisers}