mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
perf: more regex (and related areas) deallocation
This applies the commit without the playground changes (oops!)
This commit is contained in:
parent
980ebb0fbe
commit
359aeeec88
@ -21,9 +21,6 @@ function compareDirByPathLength ({ path: pathA }: { path: string }, { path: path
|
||||
return pathB.split(SLASH_SEPARATOR_RE).filter(Boolean).length - pathA.split(SLASH_SEPARATOR_RE).filter(Boolean).length
|
||||
}
|
||||
|
||||
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(?:\/(?:global|islands))?$/
|
||||
const STARTER_DOT_RE = /^\./g
|
||||
|
||||
export type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
||||
|
||||
export default defineNuxtModule<ComponentsOptions>({
|
||||
@ -78,6 +75,8 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
}))
|
||||
}
|
||||
|
||||
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(?:\/(?:global|islands))?$/
|
||||
const STARTER_DOT_RE = /^\./g
|
||||
// Resolve dirs
|
||||
nuxt.hook('app:resolve', async () => {
|
||||
// components/ dirs from all layers
|
||||
|
@ -24,20 +24,20 @@ interface ComponentChunkOptions {
|
||||
buildDir: string
|
||||
}
|
||||
|
||||
const SCRIPT_RE = /<script[^>]*>/gi
|
||||
const HAS_SLOT_OR_CLIENT_RE = /<slot[^>]*>|nuxt-client/
|
||||
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||
const IMPORT_CODE = '\nimport { mergeProps as __mergeProps } from \'vue\'' + '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
||||
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
|
||||
const KEY_RE = /:?key="[^"]"/g
|
||||
|
||||
function wrapWithVForDiv (code: string, vfor: string): string {
|
||||
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
||||
}
|
||||
|
||||
export const IslandsTransformPlugin = (options: ServerOnlyComponentTransformPluginOptions) => createUnplugin((_options, meta) => {
|
||||
const isVite = meta.framework === 'vite'
|
||||
const SCRIPT_RE = /<script[^>]*>/gi
|
||||
const HAS_SLOT_OR_CLIENT_RE = /<slot[^>]*>|nuxt-client/
|
||||
const TEMPLATE_RE = /<template>([\s\S]*)<\/template>/
|
||||
const NUXTCLIENT_ATTR_RE = /\s:?nuxt-client(="[^"]*")?/g
|
||||
const IMPORT_CODE = '\nimport { mergeProps as __mergeProps } from \'vue\'' + '\nimport { vforToArray as __vforToArray } from \'#app/components/utils\'' + '\nimport NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'' + '\nimport NuxtTeleportSsrSlot from \'#app/components/nuxt-teleport-island-slot\''
|
||||
const EXTRACTED_ATTRS_RE = /v-(?:if|else-if|else)(="[^"]*")?/g
|
||||
const KEY_RE = /:?key="[^"]"/g
|
||||
|
||||
function wrapWithVForDiv (code: string, vfor: string): string {
|
||||
return `<div v-for="${vfor}" style="display: contents;">${code}</div>`
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'nuxt:server-only-component-transform',
|
||||
enforce: 'pre',
|
||||
|
@ -15,12 +15,192 @@ interface TreeShakeTemplatePluginOptions {
|
||||
|
||||
type AcornNode<N extends Node> = N & { start: number, end: number }
|
||||
|
||||
const SSR_RENDER_RE = /ssrRenderComponent/
|
||||
const PLACEHOLDER_EXACT_RE = /^(?:fallback|placeholder)$/
|
||||
const CLIENT_ONLY_NAME_RE = /^(?:_unref\()?(?:_component_)?(?:Lazy|lazy_)?(?:client_only|ClientOnly\)?)$/
|
||||
const PARSER_OPTIONS = { sourceType: 'module', ecmaVersion: 'latest' }
|
||||
|
||||
export const TreeShakeTemplatePlugin = (options: TreeShakeTemplatePluginOptions) => createUnplugin(() => {
|
||||
const SSR_RENDER_RE = /ssrRenderComponent/
|
||||
const PLACEHOLDER_EXACT_RE = /^(?:fallback|placeholder)$/
|
||||
const CLIENT_ONLY_NAME_RE = /^(?:_unref\()?(?:_component_)?(?:Lazy|lazy_)?(?:client_only|ClientOnly\)?)$/
|
||||
const PARSER_OPTIONS = { sourceType: 'module', ecmaVersion: 'latest' }
|
||||
|
||||
/**
|
||||
* find and remove all property with the name parameter from the setup return statement and the __returned__ object
|
||||
*/
|
||||
function removeFromSetupReturn (codeAst: Program, name: string, magicString: MagicString) {
|
||||
let walkedInSetup = false
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if (walkedInSetup) {
|
||||
this.skip()
|
||||
} else if (node.type === 'Property' && node.key.type === 'Identifier' && node.key.name === 'setup' && (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression')) {
|
||||
// walk into the setup function
|
||||
walkedInSetup = true
|
||||
if (node.value.body.type === 'BlockStatement') {
|
||||
const returnStatement = node.value.body.body.find(statement => statement.type === 'ReturnStatement') as ReturnStatement
|
||||
if (returnStatement && returnStatement.argument?.type === 'ObjectExpression') {
|
||||
// remove from return statement
|
||||
removePropertyFromObject(returnStatement.argument, name, magicString)
|
||||
}
|
||||
|
||||
// remove from __returned__
|
||||
const variableList = node.value.body.body.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration')
|
||||
const returnedVariableDeclaration = variableList.find(declaration => declaration.declarations[0]?.id.type === 'Identifier' && declaration.declarations[0]?.id.name === '__returned__' && declaration.declarations[0]?.init?.type === 'ObjectExpression')
|
||||
if (returnedVariableDeclaration) {
|
||||
const init = returnedVariableDeclaration.declarations[0]?.init as ObjectExpression | undefined
|
||||
if (init) {
|
||||
removePropertyFromObject(init, name, magicString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a property from an object expression
|
||||
*/
|
||||
function removePropertyFromObject (node: ObjectExpression, name: string, magicString: MagicString) {
|
||||
for (const property of node.properties) {
|
||||
if (property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === name) {
|
||||
magicString.remove((property as AcornNode<Property>).start, (property as AcornNode<Property>).end + 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* is the node a call expression ssrRenderComponent()
|
||||
*/
|
||||
function isSsrRender (node: Node): node is AcornNode<CallExpression> {
|
||||
return node.type === 'CallExpression' && node.callee.type === 'Identifier' && SSR_RENDER_RE.test(node.callee.name)
|
||||
}
|
||||
|
||||
function removeImportDeclaration (ast: Program, importName: string, magicString: MagicString): boolean {
|
||||
for (const node of ast.body) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
const specifier = node.specifiers.find(s => s.local.name === importName)
|
||||
if (specifier) {
|
||||
if (node.specifiers.length > 1) {
|
||||
const specifierIndex = node.specifiers.findIndex(s => s.local.name === importName)
|
||||
if (specifierIndex > -1) {
|
||||
magicString.remove((node.specifiers[specifierIndex] as AcornNode<Node>).start, (node.specifiers[specifierIndex] as AcornNode<Node>).end + 1)
|
||||
node.specifiers.splice(specifierIndex, 1)
|
||||
}
|
||||
} else {
|
||||
magicString.remove((node as AcornNode<Node>).start, (node as AcornNode<Node>).end)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if the component is called else where
|
||||
* ImportDeclarations and VariableDeclarations are ignored
|
||||
* return the name of the component if is not called
|
||||
*/
|
||||
function isComponentNotCalledInSetup (codeAst: Node, name: string): string | void {
|
||||
if (name) {
|
||||
let found = false
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if ((node.type === 'Property' && node.key.type === 'Identifier' && node.value.type === 'FunctionExpression' && node.key.name === 'setup') || (node.type === 'FunctionDeclaration' && (node.id?.name === '_sfc_ssrRender' || node.id?.name === 'ssrRender'))) {
|
||||
// walk through the setup function node or the ssrRender function
|
||||
walk(node, {
|
||||
enter (node) {
|
||||
if (found || node.type === 'VariableDeclaration') {
|
||||
this.skip()
|
||||
} else if (node.type === 'Identifier' && node.name === name) {
|
||||
found = true
|
||||
} else if (node.type === 'MemberExpression') {
|
||||
// dev only with $setup or _ctx
|
||||
found = (node.property.type === 'Literal' && node.property.value === name) || (node.property.type === 'Identifier' && node.property.name === name)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
if (!found) { return name }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the component identifier being used on ssrRender callExpression
|
||||
* @param ssrRenderNode - ssrRender callExpression
|
||||
*/
|
||||
function getComponentName (ssrRenderNode: AcornNode<CallExpression>): string {
|
||||
const componentCall = ssrRenderNode.arguments[0] as Identifier | MemberExpression | CallExpression
|
||||
|
||||
if (componentCall.type === 'Identifier') {
|
||||
return componentCall.name
|
||||
} else if (componentCall.type === 'MemberExpression') {
|
||||
return (componentCall.property as Literal).value as string
|
||||
}
|
||||
return (componentCall.arguments[0] as Identifier).name
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a variable declaration within the code
|
||||
*/
|
||||
function removeVariableDeclarator (codeAst: Node, name: string, magicString: MagicString, removedNodes: WeakSet<Node>): AcornNode<Node> | void {
|
||||
// remove variables
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
for (const declarator of node.declarations) {
|
||||
const toRemove = findMatchingPatternToRemove(declarator.id as AcornNode<Pattern>, node as AcornNode<VariableDeclaration>, name, removedNodes)
|
||||
if (toRemove) {
|
||||
magicString.remove(toRemove.start, toRemove.end + 1)
|
||||
removedNodes.add(toRemove)
|
||||
return toRemove
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* find the Pattern to remove which the identifier is equal to the name parameter.
|
||||
*/
|
||||
function findMatchingPatternToRemove (node: AcornNode<Pattern>, toRemoveIfMatched: AcornNode<Node>, name: string, removedNodeSet: WeakSet<Node>): AcornNode<Node> | undefined {
|
||||
if (node.type === 'Identifier') {
|
||||
if (node.name === name) {
|
||||
return toRemoveIfMatched
|
||||
}
|
||||
} else if (node.type === 'ArrayPattern') {
|
||||
const elements = node.elements.filter((e): e is AcornNode<Pattern> => e !== null && !removedNodeSet.has(e))
|
||||
|
||||
for (const element of elements) {
|
||||
const matched = findMatchingPatternToRemove(element, elements.length > 1 ? element : toRemoveIfMatched, name, removedNodeSet)
|
||||
if (matched) { return matched }
|
||||
}
|
||||
} else if (node.type === 'ObjectPattern') {
|
||||
const properties = node.properties.filter((e): e is AssignmentProperty => e.type === 'Property' && !removedNodeSet.has(e))
|
||||
|
||||
for (const [index, property] of properties.entries()) {
|
||||
let nodeToRemove = property as AcornNode<Node>
|
||||
if (properties.length < 2) {
|
||||
nodeToRemove = toRemoveIfMatched
|
||||
}
|
||||
|
||||
const matched = findMatchingPatternToRemove(property.value as AcornNode<Pattern>, nodeToRemove as AcornNode<Node>, name, removedNodeSet)
|
||||
if (matched) {
|
||||
if (matched === property) {
|
||||
properties.splice(index, 1)
|
||||
}
|
||||
return matched
|
||||
}
|
||||
}
|
||||
} else if (node.type === 'AssignmentPattern') {
|
||||
const matched = findMatchingPatternToRemove(node.left as AcornNode<Pattern>, toRemoveIfMatched, name, removedNodeSet)
|
||||
if (matched) { return matched }
|
||||
}
|
||||
}
|
||||
|
||||
const regexpMap = new WeakMap<Component[], [RegExp, RegExp, string[]]>()
|
||||
return {
|
||||
name: 'nuxt:tree-shake-template',
|
||||
@ -116,183 +296,3 @@ export const TreeShakeTemplatePlugin = (options: TreeShakeTemplatePluginOptions)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* find and remove all property with the name parameter from the setup return statement and the __returned__ object
|
||||
*/
|
||||
function removeFromSetupReturn (codeAst: Program, name: string, magicString: MagicString) {
|
||||
let walkedInSetup = false
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if (walkedInSetup) {
|
||||
this.skip()
|
||||
} else if (node.type === 'Property' && node.key.type === 'Identifier' && node.key.name === 'setup' && (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression')) {
|
||||
// walk into the setup function
|
||||
walkedInSetup = true
|
||||
if (node.value.body.type === 'BlockStatement') {
|
||||
const returnStatement = node.value.body.body.find(statement => statement.type === 'ReturnStatement') as ReturnStatement
|
||||
if (returnStatement && returnStatement.argument?.type === 'ObjectExpression') {
|
||||
// remove from return statement
|
||||
removePropertyFromObject(returnStatement.argument, name, magicString)
|
||||
}
|
||||
|
||||
// remove from __returned__
|
||||
const variableList = node.value.body.body.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration')
|
||||
const returnedVariableDeclaration = variableList.find(declaration => declaration.declarations[0]?.id.type === 'Identifier' && declaration.declarations[0]?.id.name === '__returned__' && declaration.declarations[0]?.init?.type === 'ObjectExpression')
|
||||
if (returnedVariableDeclaration) {
|
||||
const init = returnedVariableDeclaration.declarations[0]?.init as ObjectExpression | undefined
|
||||
if (init) {
|
||||
removePropertyFromObject(init, name, magicString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a property from an object expression
|
||||
*/
|
||||
function removePropertyFromObject (node: ObjectExpression, name: string, magicString: MagicString) {
|
||||
for (const property of node.properties) {
|
||||
if (property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === name) {
|
||||
magicString.remove((property as AcornNode<Property>).start, (property as AcornNode<Property>).end + 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* is the node a call expression ssrRenderComponent()
|
||||
*/
|
||||
function isSsrRender (node: Node): node is AcornNode<CallExpression> {
|
||||
return node.type === 'CallExpression' && node.callee.type === 'Identifier' && SSR_RENDER_RE.test(node.callee.name)
|
||||
}
|
||||
|
||||
function removeImportDeclaration (ast: Program, importName: string, magicString: MagicString): boolean {
|
||||
for (const node of ast.body) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
const specifier = node.specifiers.find(s => s.local.name === importName)
|
||||
if (specifier) {
|
||||
if (node.specifiers.length > 1) {
|
||||
const specifierIndex = node.specifiers.findIndex(s => s.local.name === importName)
|
||||
if (specifierIndex > -1) {
|
||||
magicString.remove((node.specifiers[specifierIndex] as AcornNode<Node>).start, (node.specifiers[specifierIndex] as AcornNode<Node>).end + 1)
|
||||
node.specifiers.splice(specifierIndex, 1)
|
||||
}
|
||||
} else {
|
||||
magicString.remove((node as AcornNode<Node>).start, (node as AcornNode<Node>).end)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if the component is called else where
|
||||
* ImportDeclarations and VariableDeclarations are ignored
|
||||
* return the name of the component if is not called
|
||||
*/
|
||||
function isComponentNotCalledInSetup (codeAst: Node, name: string): string | void {
|
||||
if (name) {
|
||||
let found = false
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if ((node.type === 'Property' && node.key.type === 'Identifier' && node.value.type === 'FunctionExpression' && node.key.name === 'setup') || (node.type === 'FunctionDeclaration' && (node.id?.name === '_sfc_ssrRender' || node.id?.name === 'ssrRender'))) {
|
||||
// walk through the setup function node or the ssrRender function
|
||||
walk(node, {
|
||||
enter (node) {
|
||||
if (found || node.type === 'VariableDeclaration') {
|
||||
this.skip()
|
||||
} else if (node.type === 'Identifier' && node.name === name) {
|
||||
found = true
|
||||
} else if (node.type === 'MemberExpression') {
|
||||
// dev only with $setup or _ctx
|
||||
found = (node.property.type === 'Literal' && node.property.value === name) || (node.property.type === 'Identifier' && node.property.name === name)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
if (!found) { return name }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the component identifier being used on ssrRender callExpression
|
||||
* @param ssrRenderNode - ssrRender callExpression
|
||||
*/
|
||||
function getComponentName (ssrRenderNode: AcornNode<CallExpression>): string {
|
||||
const componentCall = ssrRenderNode.arguments[0] as Identifier | MemberExpression | CallExpression
|
||||
|
||||
if (componentCall.type === 'Identifier') {
|
||||
return componentCall.name
|
||||
} else if (componentCall.type === 'MemberExpression') {
|
||||
return (componentCall.property as Literal).value as string
|
||||
}
|
||||
return (componentCall.arguments[0] as Identifier).name
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a variable declaration within the code
|
||||
*/
|
||||
function removeVariableDeclarator (codeAst: Node, name: string, magicString: MagicString, removedNodes: WeakSet<Node>): AcornNode<Node> | void {
|
||||
// remove variables
|
||||
walk(codeAst, {
|
||||
enter (node) {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
for (const declarator of node.declarations) {
|
||||
const toRemove = findMatchingPatternToRemove(declarator.id as AcornNode<Pattern>, node as AcornNode<VariableDeclaration>, name, removedNodes)
|
||||
if (toRemove) {
|
||||
magicString.remove(toRemove.start, toRemove.end + 1)
|
||||
removedNodes.add(toRemove)
|
||||
return toRemove
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* find the Pattern to remove which the identifier is equal to the name parameter.
|
||||
*/
|
||||
function findMatchingPatternToRemove (node: AcornNode<Pattern>, toRemoveIfMatched: AcornNode<Node>, name: string, removedNodeSet: WeakSet<Node>): AcornNode<Node> | undefined {
|
||||
if (node.type === 'Identifier') {
|
||||
if (node.name === name) {
|
||||
return toRemoveIfMatched
|
||||
}
|
||||
} else if (node.type === 'ArrayPattern') {
|
||||
const elements = node.elements.filter((e): e is AcornNode<Pattern> => e !== null && !removedNodeSet.has(e))
|
||||
|
||||
for (const element of elements) {
|
||||
const matched = findMatchingPatternToRemove(element, elements.length > 1 ? element : toRemoveIfMatched, name, removedNodeSet)
|
||||
if (matched) { return matched }
|
||||
}
|
||||
} else if (node.type === 'ObjectPattern') {
|
||||
const properties = node.properties.filter((e): e is AssignmentProperty => e.type === 'Property' && !removedNodeSet.has(e))
|
||||
|
||||
for (const [index, property] of properties.entries()) {
|
||||
let nodeToRemove = property as AcornNode<Node>
|
||||
if (properties.length < 2) {
|
||||
nodeToRemove = toRemoveIfMatched
|
||||
}
|
||||
|
||||
const matched = findMatchingPatternToRemove(property.value as AcornNode<Pattern>, nodeToRemove as AcornNode<Node>, name, removedNodeSet)
|
||||
if (matched) {
|
||||
if (matched === property) {
|
||||
properties.splice(index, 1)
|
||||
}
|
||||
return matched
|
||||
}
|
||||
}
|
||||
} else if (node.type === 'AssignmentPattern') {
|
||||
const matched = findMatchingPatternToRemove(node.left as AcornNode<Pattern>, toRemoveIfMatched, name, removedNodeSet)
|
||||
if (matched) { return matched }
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,6 @@ import type { Component, ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
import { QUOTE_RE, resolveComponentNameSegments } from '../core/utils'
|
||||
|
||||
const ISLAND_RE = /\.island(?:\.global)?$/
|
||||
const GLOBAL_RE = /\.global(?:\.island)?$/
|
||||
const COMPONENT_MODE_RE = /(?<=\.)(client|server)(\.global|\.island)*$/
|
||||
const MODE_REPLACEMENT_RE = /(\.(client|server))?(\.global|\.island)*$/
|
||||
/**
|
||||
* Scan the components inside different components folders
|
||||
* and return a unique list of components
|
||||
@ -29,6 +25,11 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
// All scanned paths
|
||||
const scannedPaths: string[] = []
|
||||
|
||||
const ISLAND_RE = /\.island(?:\.global)?$/
|
||||
const GLOBAL_RE = /\.global(?:\.island)?$/
|
||||
const COMPONENT_MODE_RE = /(?<=\.)(client|server)(\.global|\.island)*$/
|
||||
const MODE_REPLACEMENT_RE = /(\.(client|server))?(\.global|\.island)*$/
|
||||
|
||||
for (const dir of dirs) {
|
||||
if (dir.enabled === false) {
|
||||
continue
|
||||
|
@ -102,10 +102,10 @@ export const componentsIslandsTemplate: NuxtTemplate = {
|
||||
},
|
||||
}
|
||||
|
||||
const NON_VUE_RE = /\b\.(?!vue)\w+$/g
|
||||
export const componentsTypeTemplate = {
|
||||
filename: 'components.d.ts' as const,
|
||||
getContents: ({ app, nuxt }) => {
|
||||
const NON_VUE_RE = /\b\.(?!vue)\w+$/g
|
||||
const buildDir = nuxt.options.buildDir
|
||||
const componentTypes = app.components.filter(c => !c.island).map((c) => {
|
||||
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
|
||||
|
@ -10,8 +10,8 @@ import { generateApp as _generateApp, createApp } from './app'
|
||||
import { checkForExternalConfigurationFiles } from './external-config-files'
|
||||
import { cleanupCaches, getVueHash } from './cache'
|
||||
|
||||
const IS_RESTART_PATH_RE = /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i
|
||||
export async function build (nuxt: Nuxt) {
|
||||
const IS_RESTART_PATH_RE = /^(?:app\.|error\.|plugins\/|middleware\/|layouts\/)/i
|
||||
const app = createApp(nuxt)
|
||||
nuxt.apps.default = app
|
||||
|
||||
|
@ -26,9 +26,9 @@ const logLevelMapReverse = {
|
||||
verbose: 3,
|
||||
} satisfies Record<NuxtOptions['logLevel'], NitroConfig['logLevel']>
|
||||
|
||||
const NODE_MODULES_RE = /(?<=\/)node_modules\/(.+)$/
|
||||
const PNPM_NODE_MODULES_RE = /\.pnpm\/.+\/node_modules\/(.+)$/
|
||||
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
const NODE_MODULES_RE = /(?<=\/)node_modules\/(.+)$/
|
||||
const PNPM_NODE_MODULES_RE = /\.pnpm\/.+\/node_modules\/(.+)$/
|
||||
// Resolve config
|
||||
const excludePaths = nuxt.options._layers
|
||||
.flatMap(l => [
|
||||
|
@ -613,7 +613,7 @@ export default defineNuxtPlugin({
|
||||
})`,
|
||||
})
|
||||
}
|
||||
|
||||
const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i
|
||||
nuxt.hooks.hook('builder:watch', (event, relativePath) => {
|
||||
const path = resolve(nuxt.options.srcDir, relativePath)
|
||||
// Local module patterns
|
||||
@ -791,9 +791,6 @@ async function checkDependencyVersion (name: string, nuxtVersion: string): Promi
|
||||
console.warn(`[nuxt] Expected \`${name}\` to be at least \`${nuxtVersion}\` but got \`${version}\`. This might lead to unexpected behavior. Check your package.json or refresh your lockfile.`)
|
||||
}
|
||||
}
|
||||
|
||||
const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i
|
||||
|
||||
function deduplicateArray<T = unknown> (maybeArray: T): T {
|
||||
if (!Array.isArray(maybeArray)) { return maybeArray }
|
||||
|
||||
|
@ -7,10 +7,9 @@ interface DevOnlyPluginOptions {
|
||||
sourcemap?: boolean
|
||||
}
|
||||
|
||||
const DEVONLY_COMP_SINGLE_RE = /<(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>[\s\S]*?<\/(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>/
|
||||
const DEVONLY_COMP_RE = /<(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>[\s\S]*?<\/(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>/g
|
||||
|
||||
export const DevOnlyPlugin = (options: DevOnlyPluginOptions) => createUnplugin(() => {
|
||||
const DEVONLY_COMP_SINGLE_RE = /<(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>[\s\S]*?<\/(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>/
|
||||
const DEVONLY_COMP_RE = /<(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>[\s\S]*?<\/(?:dev-only|DevOnly|lazy-dev-only|LazyDevOnly)>/g
|
||||
return {
|
||||
name: 'nuxt:server-devonly:transform',
|
||||
enforce: 'pre',
|
||||
|
@ -11,9 +11,6 @@ interface LayerAliasingOptions {
|
||||
layers: NuxtConfigLayer[]
|
||||
}
|
||||
|
||||
const ALIAS_RE = /(?<=['"])[~@]{1,2}(?=\/)/g
|
||||
const ALIAS_RE_SINGLE = /(?<=['"])[~@]{1,2}(?=\/)/
|
||||
|
||||
export const LayerAliasingPlugin = (options: LayerAliasingOptions) => createUnplugin((_options, meta) => {
|
||||
const aliases: Record<string, Record<string, string>> = {}
|
||||
for (const layer of options.layers) {
|
||||
@ -27,6 +24,8 @@ export const LayerAliasingPlugin = (options: LayerAliasingOptions) => createUnpl
|
||||
'@@': layer.config?.alias?.['@@'] || rootDir,
|
||||
}
|
||||
}
|
||||
const ALIAS_RE = /(?<=['"])[~@]{1,2}(?=\/)/g
|
||||
const ALIAS_RE_SINGLE = /(?<=['"])[~@]{1,2}(?=\/)/
|
||||
const layers = Object.keys(aliases).sort((a, b) => b.length - a.length)
|
||||
|
||||
return {
|
||||
|
@ -99,13 +99,13 @@ export const serverPluginTemplate: NuxtTemplate = {
|
||||
},
|
||||
}
|
||||
|
||||
const TS_RE = /\.[cm]?tsx?$/
|
||||
const JS_LETTER_RE = /\.(?<letter>[cm])?jsx?$/
|
||||
const JS_RE = /\.[cm]jsx?$/
|
||||
const JS_CAPTURE_RE = /\.[cm](jsx?)$/
|
||||
export const pluginsDeclaration: NuxtTemplate = {
|
||||
filename: 'types/plugins.d.ts',
|
||||
getContents: async ({ nuxt, app }) => {
|
||||
const TS_RE = /\.[cm]?tsx?$/
|
||||
const JS_LETTER_RE = /\.(?<letter>[cm])?jsx?$/
|
||||
const JS_RE = /\.[cm]jsx?$/
|
||||
const JS_CAPTURE_RE = /\.[cm](jsx?)$/
|
||||
const EXTENSION_RE = new RegExp(`(?<=\\w)(${nuxt.options.extensions.map(e => escapeRE(e)).join('|')})$`, 'g')
|
||||
|
||||
const typesDir = join(nuxt.options.buildDir, 'types')
|
||||
@ -177,12 +177,12 @@ export { }
|
||||
},
|
||||
}
|
||||
|
||||
const adHocModules = ['router', 'pages', 'imports', 'meta', 'components', 'nuxt-config-schema']
|
||||
const IMPORT_NAME_RE = /\.\w+$/
|
||||
const GIT_RE = /^git\+/
|
||||
export const schemaTemplate: NuxtTemplate = {
|
||||
filename: 'types/schema.d.ts',
|
||||
getContents: async ({ nuxt }) => {
|
||||
const adHocModules = ['router', 'pages', 'imports', 'meta', 'components', 'nuxt-config-schema']
|
||||
const IMPORT_NAME_RE = /\.\w+$/
|
||||
const GIT_RE = /^git\+/
|
||||
const relativeRoot = relative(resolve(nuxt.options.buildDir, 'types'), nuxt.options.rootDir)
|
||||
const getImportName = (name: string) => (name[0] === '.' ? './' + join(relativeRoot, name) : name).replace(IMPORT_NAME_RE, '')
|
||||
|
||||
@ -528,13 +528,12 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
||||
},
|
||||
}
|
||||
|
||||
const TYPE_FILENAME_RE = /\.([cm])?[jt]s$/
|
||||
const DECLARATION_RE = /\.d\.[cm]?ts$/
|
||||
export const buildTypeTemplate: NuxtTemplate = {
|
||||
filename: 'types/build.d.ts',
|
||||
getContents ({ app }) {
|
||||
const TYPE_FILENAME_RE = /\.([cm])?[jt]s$/
|
||||
const DECLARATION_RE = /\.d\.[cm]?ts$/
|
||||
let declarations = ''
|
||||
|
||||
for (const file of app.templates) {
|
||||
if (file.write || !file.filename || DECLARATION_RE.test(file.filename)) {
|
||||
continue
|
||||
|
@ -5,10 +5,9 @@ import { tryUseNuxt } from '@nuxt/kit'
|
||||
import type { ImportsOptions } from 'nuxt/schema'
|
||||
import { isJS, isVue } from '../core/utils'
|
||||
|
||||
const NODE_MODULES_RE = /[\\/]node_modules[\\/]/
|
||||
const IMPORTS_RE = /(['"])#imports\1/
|
||||
|
||||
export const TransformPlugin = ({ ctx, options, sourcemap }: { ctx: Unimport, options: Partial<ImportsOptions>, sourcemap?: boolean }) => createUnplugin(() => {
|
||||
const NODE_MODULES_RE = /[\\/]node_modules[\\/]/
|
||||
const IMPORTS_RE = /(['"])#imports\1/
|
||||
return {
|
||||
name: 'nuxt:imports-transform',
|
||||
enforce: 'post',
|
||||
|
@ -19,8 +19,6 @@ import { extractRouteRules, getMappedPages } from './route-rules'
|
||||
import { PageMetaPlugin } from './plugins/page-meta'
|
||||
import { RouteInjectionPlugin } from './plugins/route-injection'
|
||||
|
||||
const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/
|
||||
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'pages',
|
||||
@ -291,6 +289,7 @@ export default defineNuxtModule({
|
||||
}
|
||||
})
|
||||
|
||||
const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/
|
||||
// Record all pages for use in prerendering
|
||||
const prerenderRoutes = new Set<string>()
|
||||
|
||||
|
@ -15,14 +15,15 @@ interface PageMetaPluginOptions {
|
||||
sourcemap?: boolean
|
||||
}
|
||||
|
||||
const HAS_MACRO_RE = /\bdefinePageMeta\s*\(\s*/
|
||||
export const PageMetaPlugin = (options: PageMetaPluginOptions) => createUnplugin(() => {
|
||||
const HAS_MACRO_RE = /\bdefinePageMeta\s*\(\s*/
|
||||
|
||||
const CODE_EMPTY = `
|
||||
const CODE_EMPTY = `
|
||||
const __nuxt_page_meta = null
|
||||
export default __nuxt_page_meta
|
||||
`
|
||||
|
||||
const CODE_HMR = `
|
||||
const CODE_HMR = `
|
||||
// Vite
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(mod => {
|
||||
@ -35,8 +36,28 @@ if (import.meta.webpackHot) {
|
||||
if (err) { window.location = window.location.href }
|
||||
})
|
||||
}`
|
||||
// https://github.com/vuejs/vue-loader/pull/1911
|
||||
// https://github.com/vitejs/vite/issues/8473
|
||||
const QUERY_START_RE = /^\?/
|
||||
const MACRO_RE = /¯o=true/
|
||||
function rewriteQuery (id: string) {
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(QUERY_START_RE, '').replace(MACRO_RE, ''))
|
||||
}
|
||||
|
||||
function parseMacroQuery (id: string) {
|
||||
const { search } = parseURL(decodeURIComponent(isAbsolute(id) ? pathToFileURL(id).href : id).replace(/\?macro=true$/, ''))
|
||||
const query = parseQuery(search)
|
||||
if (id.includes('?macro=true')) {
|
||||
return { macro: 'true', ...query }
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
const QUOTED_SPECIFIER_RE = /(["']).*\1/
|
||||
function getQuotedSpecifier (id: string) {
|
||||
return id.match(QUOTED_SPECIFIER_RE)?.[0]
|
||||
}
|
||||
|
||||
export const PageMetaPlugin = (options: PageMetaPluginOptions) => createUnplugin(() => {
|
||||
return {
|
||||
name: 'nuxt:pages-macros-transform',
|
||||
enforce: 'post',
|
||||
@ -173,25 +194,3 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions) => createUnplugin
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// https://github.com/vuejs/vue-loader/pull/1911
|
||||
// https://github.com/vitejs/vite/issues/8473
|
||||
const QUERY_START_RE = /^\?/
|
||||
const MACRO_RE = /¯o=true/
|
||||
function rewriteQuery (id: string) {
|
||||
return id.replace(/\?.+$/, r => '?macro=true&' + r.replace(QUERY_START_RE, '').replace(MACRO_RE, ''))
|
||||
}
|
||||
|
||||
function parseMacroQuery (id: string) {
|
||||
const { search } = parseURL(decodeURIComponent(isAbsolute(id) ? pathToFileURL(id).href : id).replace(/\?macro=true$/, ''))
|
||||
const query = parseQuery(search)
|
||||
if (id.includes('?macro=true')) {
|
||||
return { macro: 'true', ...query }
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
const QUOTED_SPECIFIER_RE = /(["']).*\1/
|
||||
function getQuotedSpecifier (id: string) {
|
||||
return id.match(QUOTED_SPECIFIER_RE)?.[0]
|
||||
}
|
||||
|
@ -4,12 +4,11 @@ import type { Nuxt } from '@nuxt/schema'
|
||||
import { stripLiteral } from 'strip-literal'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g
|
||||
const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g
|
||||
|
||||
const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/
|
||||
|
||||
export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
||||
const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g
|
||||
const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g
|
||||
|
||||
const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/
|
||||
return {
|
||||
name: 'nuxt:route-injection-plugin',
|
||||
enforce: 'post',
|
||||
|
@ -17,11 +17,6 @@ interface ComposableKeysOptions {
|
||||
composables: Array<{ name: string, source?: string | RegExp, argumentLength: number }>
|
||||
}
|
||||
|
||||
const stringTypes: Array<string | undefined> = ['Literal', 'TemplateLiteral']
|
||||
const NUXT_LIB_RE = /node_modules\/(?:nuxt|nuxt3|nuxt-nightly)\//
|
||||
const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
|
||||
const SCRIPT_RE = /(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i
|
||||
|
||||
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => {
|
||||
const composableMeta: Record<string, any> = {}
|
||||
const composableLengths = new Set<number>()
|
||||
@ -35,6 +30,10 @@ export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptio
|
||||
const maxLength = Math.max(...composableLengths)
|
||||
const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`)
|
||||
|
||||
const stringTypes: Array<string | undefined> = ['Literal', 'TemplateLiteral']
|
||||
const NUXT_LIB_RE = /node_modules\/(?:nuxt|nuxt3|nuxt-nightly)\//
|
||||
const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
|
||||
const SCRIPT_RE = /(?<=<script[^>]*>)[\s\S]*?(?=<\/script>)/i
|
||||
return {
|
||||
name: 'nuxt:composable-keys',
|
||||
enforce: 'post',
|
||||
|
@ -7,11 +7,6 @@ import { dirname, relative } from 'pathe'
|
||||
import MagicString from 'magic-string'
|
||||
import { isCSSRequest } from 'vite'
|
||||
|
||||
const PREFIX = 'virtual:public?'
|
||||
const CSS_URL_RE = /url\((\/[^)]+)\)/g
|
||||
const CSS_URL_SINGLE_RE = /url\(\/[^)]+\)/
|
||||
const RENDER_CHUNK_RE = /(?<= = )['"`]/
|
||||
|
||||
interface VitePublicDirsPluginOptions {
|
||||
dev?: boolean
|
||||
sourcemap?: boolean
|
||||
@ -21,6 +16,11 @@ interface VitePublicDirsPluginOptions {
|
||||
export const VitePublicDirsPlugin = createUnplugin((options: VitePublicDirsPluginOptions) => {
|
||||
const { resolveFromPublicAssets } = useResolveFromPublicAssets()
|
||||
|
||||
const PREFIX = 'virtual:public?'
|
||||
const CSS_URL_RE = /url\((\/[^)]+)\)/g
|
||||
const CSS_URL_SINGLE_RE = /url\(\/[^)]+\)/
|
||||
const RENDER_CHUNK_RE = /(?<= = )['"`]/
|
||||
|
||||
const devTransformPlugin: UnpluginOptions = {
|
||||
name: 'nuxt:vite-public-dir-resolution-dev',
|
||||
vite: {
|
||||
|
Loading…
Reference in New Issue
Block a user