mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat!(nuxt3): extends support for components/
directory (#3108)
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
This commit is contained in:
parent
c9c0171b2d
commit
790a54897a
@ -6,6 +6,8 @@ const themeConfig = useRuntimeConfig().theme
|
|||||||
<NuxtExampleLayout example="config-extends">
|
<NuxtExampleLayout example="config-extends">
|
||||||
theme runtimeConfig
|
theme runtimeConfig
|
||||||
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
||||||
|
<BaseButton>Base Button</BaseButton>
|
||||||
|
<FancyButton>Fancy Button</FancyButton>
|
||||||
</NuxtExampleLayout>
|
</NuxtExampleLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
5
examples/config-extends/base/components/BaseButton.vue
Normal file
5
examples/config-extends/base/components/BaseButton.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<button role="button">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
5
examples/config-extends/base/components/FancyButton.vue
Normal file
5
examples/config-extends/base/components/FancyButton.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<BaseButton>
|
||||||
|
<slot />
|
||||||
|
</BaseButton>
|
||||||
|
</template>
|
16
examples/config-extends/components/FancyButton.vue
Normal file
16
examples/config-extends/components/FancyButton.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<BaseButton class="fancy-button">
|
||||||
|
<slot />
|
||||||
|
</BaseButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
button {
|
||||||
|
appearance: none;
|
||||||
|
background-color: #FAFBFC;
|
||||||
|
border: 1px solid rgba(27, 31, 35, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: rgba(27, 31, 35, 0.04) 0 1px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset;
|
||||||
|
color: #24292E;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
import { statSync } from 'fs'
|
import { statSync } from 'fs'
|
||||||
import { resolve } from 'pathe'
|
import { resolve, basename } from 'pathe'
|
||||||
import { defineNuxtModule, resolveAlias, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
|
import { defineNuxtModule, resolveAlias, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
|
||||||
import type { Component, ComponentsDir, ComponentsOptions } from '@nuxt/schema'
|
import type { Component, ComponentsDir, ComponentsOptions } from '@nuxt/schema'
|
||||||
import { componentsTemplate, componentsTypeTemplate } from './templates'
|
import { componentsTemplate, componentsTypeTemplate } from './templates'
|
||||||
@ -8,6 +8,9 @@ import { loaderPlugin } from './loader'
|
|||||||
|
|
||||||
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
|
||||||
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch (_e) { return false } }
|
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch (_e) { return false } }
|
||||||
|
function compareDirByPathLength ({ path: pathA }, { path: pathB }) {
|
||||||
|
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
||||||
|
}
|
||||||
|
|
||||||
export default defineNuxtModule<ComponentsOptions>({
|
export default defineNuxtModule<ComponentsOptions>({
|
||||||
meta: {
|
meta: {
|
||||||
@ -17,15 +20,40 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
defaults: {
|
defaults: {
|
||||||
dirs: ['~/components']
|
dirs: ['~/components']
|
||||||
},
|
},
|
||||||
setup (options, nuxt) {
|
setup (componentOptions, nuxt) {
|
||||||
let componentDirs = []
|
let componentDirs = []
|
||||||
let components: Component[] = []
|
let components: Component[] = []
|
||||||
|
|
||||||
|
const normalizeDirs = (dir: any, cwd: string) => {
|
||||||
|
if (Array.isArray(dir)) {
|
||||||
|
return dir.map(dir => normalizeDirs(dir, cwd)).flat().sort(compareDirByPathLength)
|
||||||
|
}
|
||||||
|
if (dir === true || dir === undefined) {
|
||||||
|
return [{ path: resolve(cwd, 'components') }]
|
||||||
|
}
|
||||||
|
if (typeof dir === 'string') {
|
||||||
|
return {
|
||||||
|
path: resolve(cwd, resolveAlias(dir, {
|
||||||
|
...nuxt.options.alias,
|
||||||
|
'~': cwd
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve dirs
|
// Resolve dirs
|
||||||
nuxt.hook('app:resolve', async () => {
|
nuxt.hook('app:resolve', async () => {
|
||||||
await nuxt.callHook('components:dirs', options.dirs)
|
const allDirs = [
|
||||||
|
...normalizeDirs(componentOptions.dirs, nuxt.options.srcDir),
|
||||||
|
...nuxt.options._extends
|
||||||
|
.map(layer => normalizeDirs(layer.config.components, layer.cwd))
|
||||||
|
.flat()
|
||||||
|
]
|
||||||
|
|
||||||
componentDirs = options.dirs.filter(isPureObjectOrString).map((dir) => {
|
await nuxt.callHook('components:dirs', allDirs)
|
||||||
|
|
||||||
|
componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => {
|
||||||
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir }
|
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir }
|
||||||
const dirPath = resolveAlias(dirOptions.path, nuxt.options.alias)
|
const dirPath = resolveAlias(dirOptions.path, nuxt.options.alias)
|
||||||
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
||||||
@ -34,7 +62,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
dirOptions.level = Number(dirOptions.level || 0)
|
dirOptions.level = Number(dirOptions.level || 0)
|
||||||
|
|
||||||
const present = isDirectory(dirPath)
|
const present = isDirectory(dirPath)
|
||||||
if (!present && dirOptions.path !== '~/components') {
|
if (!present && basename(dirOptions.path) !== 'components') {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn('Components directory not found: `' + dirPath + '`')
|
console.warn('Components directory not found: `' + dirPath + '`')
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { basename, extname, join, dirname, relative } from 'pathe'
|
import { basename, extname, join, dirname, relative } from 'pathe'
|
||||||
import { globby } from 'globby'
|
import { globby } from 'globby'
|
||||||
import { pascalCase, splitByCase } from 'scule'
|
import { pascalCase, splitByCase } from 'scule'
|
||||||
import type { ScanDir, Component, ComponentsDir } from '@nuxt/schema'
|
import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||||
|
|
||||||
export function sortDirsByPathLength ({ path: pathA }: ScanDir, { path: pathB }: ScanDir): number {
|
|
||||||
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
|
||||||
}
|
|
||||||
|
|
||||||
// vue@2 src/shared/util.js
|
// vue@2 src/shared/util.js
|
||||||
// TODO: update to vue3?
|
// TODO: update to vue3?
|
||||||
function hyphenate (str: string):string {
|
function hyphenate (str: string): string {
|
||||||
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
|
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +27,7 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
|||||||
// All scanned paths
|
// All scanned paths
|
||||||
const scannedPaths: string[] = []
|
const scannedPaths: string[] = []
|
||||||
|
|
||||||
for (const dir of dirs.sort(sortDirsByPathLength)) {
|
for (const dir of dirs) {
|
||||||
// A map from resolved path to component name (used for making duplicate warning message)
|
// A map from resolved path to component name (used for making duplicate warning message)
|
||||||
const resolvedNames = new Map<string, string>()
|
const resolvedNames = new Map<string, string>()
|
||||||
|
|
||||||
@ -112,7 +108,6 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
|||||||
shortPath,
|
shortPath,
|
||||||
export: 'default',
|
export: 'default',
|
||||||
global: dir.global,
|
global: dir.global,
|
||||||
level: Number(dir.level),
|
|
||||||
prefetch: Boolean(dir.prefetch),
|
prefetch: Boolean(dir.prefetch),
|
||||||
preload: Boolean(dir.preload)
|
preload: Boolean(dir.preload)
|
||||||
}
|
}
|
||||||
@ -121,11 +116,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
|||||||
component = (await dir.extendComponent(component)) || component
|
component = (await dir.extendComponent(component)) || component
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if component is already defined, used to overwite if level is inferiour
|
// Ignore component if component is already defined
|
||||||
const definedComponent = components.find(c => c.pascalName === component.pascalName)
|
if (!components.find(c => c.pascalName === component.pascalName)) {
|
||||||
if (definedComponent && component.level < definedComponent.level) {
|
|
||||||
Object.assign(definedComponent, component)
|
|
||||||
} else if (!definedComponent) {
|
|
||||||
components.push(component)
|
components.push(component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ const rFixture = (...p) => resolve(fixtureDir, ...p)
|
|||||||
const dirs: ComponentsDir[] = [
|
const dirs: ComponentsDir[] = [
|
||||||
{
|
{
|
||||||
path: rFixture('components'),
|
path: rFixture('components'),
|
||||||
level: 0,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
extensions: [
|
extensions: [
|
||||||
'vue'
|
'vue'
|
||||||
@ -24,7 +23,6 @@ const dirs: ComponentsDir[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: rFixture('components'),
|
path: rFixture('components'),
|
||||||
level: 0,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
extensions: [
|
extensions: [
|
||||||
'vue'
|
'vue'
|
||||||
@ -43,7 +41,6 @@ const dirs: ComponentsDir[] = [
|
|||||||
'vue'
|
'vue'
|
||||||
],
|
],
|
||||||
prefix: 'nuxt',
|
prefix: 'nuxt',
|
||||||
level: 0,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
pattern: '**/*.{vue,}',
|
pattern: '**/*.{vue,}',
|
||||||
ignore: [
|
ignore: [
|
||||||
@ -63,7 +60,6 @@ const expectedComponents = [
|
|||||||
shortPath: 'components/HelloWorld.vue',
|
shortPath: 'components/HelloWorld.vue',
|
||||||
export: 'default',
|
export: 'default',
|
||||||
global: undefined,
|
global: undefined,
|
||||||
level: 0,
|
|
||||||
prefetch: false,
|
prefetch: false,
|
||||||
preload: false
|
preload: false
|
||||||
},
|
},
|
||||||
@ -74,7 +70,6 @@ const expectedComponents = [
|
|||||||
shortPath: 'components/Nuxt3.vue',
|
shortPath: 'components/Nuxt3.vue',
|
||||||
export: 'default',
|
export: 'default',
|
||||||
global: undefined,
|
global: undefined,
|
||||||
level: 0,
|
|
||||||
prefetch: false,
|
prefetch: false,
|
||||||
preload: false
|
preload: false
|
||||||
},
|
},
|
||||||
@ -85,7 +80,6 @@ const expectedComponents = [
|
|||||||
shortPath: 'components/parent-folder/index.vue',
|
shortPath: 'components/parent-folder/index.vue',
|
||||||
export: 'default',
|
export: 'default',
|
||||||
global: undefined,
|
global: undefined,
|
||||||
level: 0,
|
|
||||||
prefetch: false,
|
prefetch: false,
|
||||||
preload: false
|
preload: false
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,12 @@ export default {
|
|||||||
*/
|
*/
|
||||||
components: {
|
components: {
|
||||||
$resolve: (val, get) => {
|
$resolve: (val, get) => {
|
||||||
if (!val) {
|
if (Array.isArray(val)) {
|
||||||
// Nuxt 2 and Nuxt 3 have different default values when this option is not set,
|
return { dirs: val }
|
||||||
// so we defer to the module's own defaults here.
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
if (val === undefined || val === true) {
|
if (val === undefined || val === true) {
|
||||||
return { dirs: ['~/components'] }
|
return { dirs: ['~/components'] }
|
||||||
}
|
}
|
||||||
if (Array.isArray(val)) {
|
|
||||||
return { dirs: val }
|
|
||||||
}
|
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,11 +5,12 @@ export interface Component {
|
|||||||
filePath: string
|
filePath: string
|
||||||
shortPath: string
|
shortPath: string
|
||||||
chunkName: string
|
chunkName: string
|
||||||
level: number
|
|
||||||
prefetch: boolean
|
prefetch: boolean
|
||||||
preload: boolean
|
preload: boolean
|
||||||
global?: boolean
|
global?: boolean
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
|
level?: number
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
import?: string
|
import?: string
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
@ -46,6 +47,7 @@ export interface ScanDir {
|
|||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
/**
|
/**
|
||||||
* Level is used to define a hint when overwriting the components which have the same name in two different directories.
|
* Level is used to define a hint when overwriting the components which have the same name in two different directories.
|
||||||
|
* @deprecated Not used by Nuxt 3 anymore
|
||||||
*/
|
*/
|
||||||
level?: number
|
level?: number
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { defineNuxtConfig } from 'nuxt3'
|
import { defineNuxtConfig } from 'nuxt3'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
extends: './base',
|
||||||
modules: [
|
modules: [
|
||||||
'@nuxt/ui'
|
'@nuxt/ui'
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user