2022-04-15 15:19:05 +00:00
|
|
|
import { statSync } from 'node:fs'
|
2022-08-15 13:10:08 +00:00
|
|
|
import { relative, resolve } from 'pathe'
|
2022-10-24 08:53:02 +00:00
|
|
|
import { defineNuxtModule, resolveAlias, addTemplate, addPluginTemplate, updateTemplates } from '@nuxt/kit'
|
2023-03-11 21:16:01 +00:00
|
|
|
import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema'
|
|
|
|
|
2022-10-11 15:26:03 +00:00
|
|
|
import { distDir } from '../dirs'
|
2023-03-08 21:13:06 +00:00
|
|
|
import { clientFallbackAutoIdPlugin } from './client-fallback-auto-id'
|
2022-11-24 12:24:14 +00:00
|
|
|
import { componentsPluginTemplate, componentsTemplate, componentsIslandsTemplate, componentsTypeTemplate } from './templates'
|
2021-06-18 16:50:03 +00:00
|
|
|
import { scanComponents } from './scan'
|
2021-07-28 12:11:32 +00:00
|
|
|
import { loaderPlugin } from './loader'
|
2022-07-14 17:46:12 +00:00
|
|
|
import { TreeShakeTemplatePlugin } from './tree-shake'
|
2021-06-18 16:50:03 +00:00
|
|
|
|
|
|
|
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
2021-08-09 21:54:44 +00:00
|
|
|
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch (_e) { return false } }
|
2022-09-07 11:32:10 +00:00
|
|
|
function compareDirByPathLength ({ path: pathA }: { path: string }, { path: pathB }: { path: string }) {
|
2022-02-07 20:48:25 +00:00
|
|
|
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
|
|
|
}
|
2021-06-18 16:50:03 +00:00
|
|
|
|
2022-11-24 12:24:14 +00:00
|
|
|
const DEFAULT_COMPONENTS_DIRS_RE = /\/components(\/global|\/islands)?$/
|
2022-07-27 13:05:34 +00:00
|
|
|
|
|
|
|
type getComponentsT = (mode?: 'client' | 'server' | 'all') => Component[]
|
|
|
|
|
2021-11-19 12:22:27 +00:00
|
|
|
export default defineNuxtModule<ComponentsOptions>({
|
2022-01-05 18:09:53 +00:00
|
|
|
meta: {
|
|
|
|
name: 'components',
|
|
|
|
configKey: 'components'
|
|
|
|
},
|
2021-06-18 16:50:03 +00:00
|
|
|
defaults: {
|
2022-02-24 16:20:49 +00:00
|
|
|
dirs: []
|
2021-06-18 16:50:03 +00:00
|
|
|
},
|
2022-02-07 20:48:25 +00:00
|
|
|
setup (componentOptions, nuxt) {
|
2022-08-22 10:12:02 +00:00
|
|
|
let componentDirs: ComponentsDir[] = []
|
2022-07-27 13:05:34 +00:00
|
|
|
const context = {
|
|
|
|
components: [] as Component[]
|
|
|
|
}
|
|
|
|
|
|
|
|
const getComponents: getComponentsT = (mode) => {
|
|
|
|
return (mode && mode !== 'all')
|
|
|
|
? context.components.filter(c => c.mode === mode || c.mode === 'all')
|
|
|
|
: context.components
|
|
|
|
}
|
2021-06-18 16:50:03 +00:00
|
|
|
|
2022-08-22 10:12:02 +00:00
|
|
|
const normalizeDirs = (dir: any, cwd: string): ComponentsDir[] => {
|
2022-02-07 20:48:25 +00:00
|
|
|
if (Array.isArray(dir)) {
|
|
|
|
return dir.map(dir => normalizeDirs(dir, cwd)).flat().sort(compareDirByPathLength)
|
|
|
|
}
|
|
|
|
if (dir === true || dir === undefined) {
|
2022-07-27 13:05:34 +00:00
|
|
|
return [
|
2022-11-24 12:24:14 +00:00
|
|
|
{ path: resolve(cwd, 'components/islands'), island: true },
|
2022-07-27 13:05:34 +00:00
|
|
|
{ path: resolve(cwd, 'components/global'), global: true },
|
|
|
|
{ path: resolve(cwd, 'components') }
|
|
|
|
]
|
2022-02-07 20:48:25 +00:00
|
|
|
}
|
|
|
|
if (typeof dir === 'string') {
|
2022-08-22 10:12:02 +00:00
|
|
|
return [
|
|
|
|
{ path: resolve(cwd, resolveAlias(dir)) }
|
|
|
|
]
|
2022-02-07 20:48:25 +00:00
|
|
|
}
|
2022-03-16 20:36:30 +00:00
|
|
|
if (!dir) {
|
|
|
|
return []
|
2022-02-24 16:20:49 +00:00
|
|
|
}
|
2022-08-22 10:12:02 +00:00
|
|
|
const dirs: ComponentsDir[] = (dir.dirs || [dir]).map((dir: any): ComponentsDir => typeof dir === 'string' ? { path: dir } : dir).filter((_dir: ComponentsDir) => _dir.path)
|
2022-03-16 20:36:30 +00:00
|
|
|
return dirs.map(_dir => ({
|
|
|
|
..._dir,
|
2022-03-22 10:35:16 +00:00
|
|
|
path: resolve(cwd, resolveAlias(_dir.path))
|
2022-03-16 20:36:30 +00:00
|
|
|
}))
|
2022-02-07 20:48:25 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 16:50:03 +00:00
|
|
|
// Resolve dirs
|
|
|
|
nuxt.hook('app:resolve', async () => {
|
2022-03-16 20:36:30 +00:00
|
|
|
// components/ dirs from all layers
|
|
|
|
const allDirs = nuxt.options._layers
|
2022-03-22 10:35:16 +00:00
|
|
|
.map(layer => normalizeDirs(layer.config.components, layer.config.srcDir))
|
2022-03-16 20:36:30 +00:00
|
|
|
.flat()
|
2022-02-07 20:48:25 +00:00
|
|
|
|
|
|
|
await nuxt.callHook('components:dirs', allDirs)
|
2021-06-18 16:50:03 +00:00
|
|
|
|
2022-02-07 20:48:25 +00:00
|
|
|
componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => {
|
2021-06-18 16:50:03 +00:00
|
|
|
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir }
|
2022-02-07 21:00:20 +00:00
|
|
|
const dirPath = resolveAlias(dirOptions.path)
|
2021-06-18 16:50:03 +00:00
|
|
|
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
2021-11-02 15:27:42 +00:00
|
|
|
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, ''))
|
2021-06-18 16:50:03 +00:00
|
|
|
|
2021-06-21 11:50:28 +00:00
|
|
|
const present = isDirectory(dirPath)
|
2022-07-27 13:05:34 +00:00
|
|
|
if (!present && !DEFAULT_COMPONENTS_DIRS_RE.test(dirOptions.path)) {
|
2021-06-18 16:50:03 +00:00
|
|
|
console.warn('Components directory not found: `' + dirPath + '`')
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2022-02-18 09:37:11 +00:00
|
|
|
global: componentOptions.global,
|
2021-06-18 16:50:03 +00:00
|
|
|
...dirOptions,
|
2021-06-21 11:50:28 +00:00
|
|
|
// TODO: https://github.com/nuxt/framework/pull/251
|
|
|
|
enabled: true,
|
2021-06-18 16:50:03 +00:00
|
|
|
path: dirPath,
|
|
|
|
extensions,
|
|
|
|
pattern: dirOptions.pattern || `**/*.{${extensions.join(',')},}`,
|
|
|
|
ignore: [
|
|
|
|
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', // ignore mixins
|
|
|
|
'**/*.d.ts', // .d.ts files
|
|
|
|
...(dirOptions.ignore || [])
|
|
|
|
],
|
|
|
|
transpile: (transpile === 'auto' ? dirPath.includes('node_modules') : transpile)
|
|
|
|
}
|
|
|
|
}).filter(d => d.enabled)
|
|
|
|
|
2022-08-09 09:13:54 +00:00
|
|
|
componentDirs = [
|
|
|
|
...componentDirs.filter(dir => !dir.path.includes('node_modules')),
|
|
|
|
...componentDirs.filter(dir => dir.path.includes('node_modules'))
|
|
|
|
]
|
|
|
|
|
2021-06-18 16:50:03 +00:00
|
|
|
nuxt.options.build!.transpile!.push(...componentDirs.filter(dir => dir.transpile).map(dir => dir.path))
|
|
|
|
})
|
|
|
|
|
2022-07-27 13:05:34 +00:00
|
|
|
// components.d.ts
|
|
|
|
addTemplate({ ...componentsTypeTemplate, options: { getComponents } })
|
|
|
|
// components.plugin.mjs
|
2022-08-22 10:12:02 +00:00
|
|
|
addPluginTemplate({ ...componentsPluginTemplate, options: { getComponents } } as any)
|
2022-07-27 13:05:34 +00:00
|
|
|
// components.server.mjs
|
|
|
|
addTemplate({ ...componentsTemplate, filename: 'components.server.mjs', options: { getComponents, mode: 'server' } })
|
|
|
|
// components.client.mjs
|
|
|
|
addTemplate({ ...componentsTemplate, filename: 'components.client.mjs', options: { getComponents, mode: 'client' } })
|
2022-11-24 12:24:14 +00:00
|
|
|
// components.islands.mjs
|
|
|
|
if (nuxt.options.experimental.componentIslands) {
|
|
|
|
addTemplate({ ...componentsIslandsTemplate, filename: 'components.islands.mjs', options: { getComponents } })
|
|
|
|
} else {
|
|
|
|
addTemplate({ filename: 'components.islands.mjs', getContents: () => 'export default {}' })
|
|
|
|
}
|
2022-01-27 12:46:28 +00:00
|
|
|
|
2022-07-27 13:05:34 +00:00
|
|
|
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
|
|
|
|
const mode = isClient ? 'client' : 'server'
|
2022-10-24 08:53:02 +00:00
|
|
|
; (config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`)
|
2022-04-07 11:03:37 +00:00
|
|
|
})
|
2022-07-27 13:05:34 +00:00
|
|
|
nuxt.hook('webpack:config', (configs) => {
|
|
|
|
for (const config of configs) {
|
|
|
|
const mode = config.name === 'server' ? 'server' : 'client'
|
2022-10-24 08:53:02 +00:00
|
|
|
; (config.resolve!.alias as any)['#components'] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`)
|
2022-07-27 13:05:34 +00:00
|
|
|
}
|
2022-02-25 10:16:24 +00:00
|
|
|
})
|
2021-06-18 16:50:03 +00:00
|
|
|
|
2022-08-30 14:41:11 +00:00
|
|
|
// Do not prefetch global components chunks
|
|
|
|
nuxt.hook('build:manifest', (manifest) => {
|
|
|
|
const sourceFiles = getComponents().filter(c => c.global).map(c => relative(nuxt.options.srcDir, c.filePath))
|
|
|
|
|
|
|
|
for (const key in manifest) {
|
|
|
|
if (manifest[key].isEntry) {
|
|
|
|
manifest[key].dynamicImports =
|
|
|
|
manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-03-09 11:46:08 +00:00
|
|
|
// Restart dev server when component directories are added/removed
|
|
|
|
nuxt.hook('builder:watch', (event, path) => {
|
|
|
|
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
|
|
|
|
const fullPath = resolve(nuxt.options.srcDir, path)
|
|
|
|
|
|
|
|
if (isDirChange && componentDirs.some(dir => dir.path === fullPath)) {
|
|
|
|
console.info(`Directory \`${path}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
|
|
|
return nuxt.callHook('restart')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-02-25 10:16:24 +00:00
|
|
|
// Scan components and add to plugin
|
|
|
|
nuxt.hook('app:templates', async () => {
|
2022-07-27 13:05:34 +00:00
|
|
|
const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir!)
|
|
|
|
await nuxt.callHook('components:extend', newComponents)
|
2022-10-11 15:26:03 +00:00
|
|
|
// add server placeholder for .client components server side. issue: #7085
|
|
|
|
for (const component of newComponents) {
|
|
|
|
if (component.mode === 'client' && !newComponents.some(c => c.pascalName === component.pascalName && c.mode === 'server')) {
|
|
|
|
newComponents.push({
|
|
|
|
...component,
|
|
|
|
mode: 'server',
|
|
|
|
filePath: resolve(distDir, 'app/components/server-placeholder'),
|
|
|
|
chunkName: 'components/' + component.kebabName
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-07-27 13:05:34 +00:00
|
|
|
context.components = newComponents
|
2021-06-18 16:50:03 +00:00
|
|
|
})
|
|
|
|
|
2022-08-15 13:10:08 +00:00
|
|
|
nuxt.hook('prepare:types', ({ references, tsConfig }) => {
|
|
|
|
tsConfig.compilerOptions!.paths['#components'] = [relative(nuxt.options.rootDir, resolve(nuxt.options.buildDir, 'components'))]
|
2022-04-07 11:03:37 +00:00
|
|
|
references.push({ path: resolve(nuxt.options.buildDir, 'components.d.ts') })
|
2021-08-09 21:54:44 +00:00
|
|
|
})
|
|
|
|
|
2021-06-18 16:50:03 +00:00
|
|
|
// Watch for changes
|
|
|
|
nuxt.hook('builder:watch', async (event, path) => {
|
|
|
|
if (!['add', 'unlink'].includes(event)) {
|
|
|
|
return
|
|
|
|
}
|
2022-08-12 09:11:09 +00:00
|
|
|
const fPath = resolve(nuxt.options.srcDir, path)
|
2021-06-18 16:50:03 +00:00
|
|
|
if (componentDirs.find(dir => fPath.startsWith(dir.path))) {
|
2022-10-24 08:53:02 +00:00
|
|
|
await updateTemplates({
|
|
|
|
filter: template => [
|
|
|
|
'components.plugin.mjs',
|
|
|
|
'components.d.ts',
|
|
|
|
'components.server.mjs',
|
|
|
|
'components.client.mjs'
|
|
|
|
].includes(template.filename)
|
|
|
|
})
|
2021-06-18 16:50:03 +00:00
|
|
|
}
|
|
|
|
})
|
2021-07-28 12:11:32 +00:00
|
|
|
|
2022-09-07 11:32:10 +00:00
|
|
|
nuxt.hook('vite:extendConfig', (config, { isClient, isServer }) => {
|
|
|
|
const mode = isClient ? 'client' : 'server'
|
|
|
|
|
2022-04-19 19:13:55 +00:00
|
|
|
config.plugins = config.plugins || []
|
2022-09-07 11:32:10 +00:00
|
|
|
if (nuxt.options.experimental.treeshakeClientOnly && isServer) {
|
2022-07-27 13:05:34 +00:00
|
|
|
config.plugins.push(TreeShakeTemplatePlugin.vite({
|
2022-09-07 11:32:10 +00:00
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
2022-07-27 13:05:34 +00:00
|
|
|
getComponents
|
|
|
|
}))
|
2022-07-17 13:13:04 +00:00
|
|
|
}
|
2023-03-08 21:13:06 +00:00
|
|
|
config.plugins.push(clientFallbackAutoIdPlugin.vite({
|
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
|
|
rootDir: nuxt.options.rootDir
|
|
|
|
}))
|
2023-02-08 08:59:57 +00:00
|
|
|
config.plugins.push(loaderPlugin.vite({
|
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
|
|
getComponents,
|
|
|
|
mode,
|
2023-03-03 10:40:24 +00:00
|
|
|
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,
|
2023-02-08 08:59:57 +00:00
|
|
|
experimentalComponentIslands: nuxt.options.experimental.componentIslands
|
|
|
|
}))
|
2022-04-19 19:13:55 +00:00
|
|
|
})
|
|
|
|
nuxt.hook('webpack:config', (configs) => {
|
|
|
|
configs.forEach((config) => {
|
2022-09-07 11:32:10 +00:00
|
|
|
const mode = config.name === 'client' ? 'client' : 'server'
|
2022-04-19 19:13:55 +00:00
|
|
|
config.plugins = config.plugins || []
|
2022-09-07 11:32:10 +00:00
|
|
|
if (nuxt.options.experimental.treeshakeClientOnly && mode === 'server') {
|
2022-07-27 13:05:34 +00:00
|
|
|
config.plugins.push(TreeShakeTemplatePlugin.webpack({
|
2022-09-07 11:32:10 +00:00
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
2022-07-27 13:05:34 +00:00
|
|
|
getComponents
|
|
|
|
}))
|
2022-07-17 13:13:04 +00:00
|
|
|
}
|
2023-03-08 21:13:06 +00:00
|
|
|
config.plugins.push(clientFallbackAutoIdPlugin.webpack({
|
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
|
|
rootDir: nuxt.options.rootDir
|
|
|
|
}))
|
2023-02-08 08:59:57 +00:00
|
|
|
config.plugins.push(loaderPlugin.webpack({
|
|
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
|
|
getComponents,
|
|
|
|
mode,
|
2023-03-03 10:40:24 +00:00
|
|
|
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,
|
2023-02-08 08:59:57 +00:00
|
|
|
experimentalComponentIslands: nuxt.options.experimental.componentIslands
|
|
|
|
}))
|
2022-04-19 19:13:55 +00:00
|
|
|
})
|
|
|
|
})
|
2021-06-18 16:50:03 +00:00
|
|
|
}
|
|
|
|
})
|