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">
|
||||
theme runtimeConfig
|
||||
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
||||
<BaseButton>Base Button</BaseButton>
|
||||
<FancyButton>Fancy Button</FancyButton>
|
||||
</NuxtExampleLayout>
|
||||
</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 { resolve } from 'pathe'
|
||||
import { resolve, basename } from 'pathe'
|
||||
import { defineNuxtModule, resolveAlias, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
|
||||
import type { Component, ComponentsDir, ComponentsOptions } from '@nuxt/schema'
|
||||
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 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>({
|
||||
meta: {
|
||||
@ -17,15 +20,40 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
defaults: {
|
||||
dirs: ['~/components']
|
||||
},
|
||||
setup (options, nuxt) {
|
||||
setup (componentOptions, nuxt) {
|
||||
let componentDirs = []
|
||||
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
|
||||
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 dirPath = resolveAlias(dirOptions.path, nuxt.options.alias)
|
||||
const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
|
||||
@ -34,7 +62,7 @@ export default defineNuxtModule<ComponentsOptions>({
|
||||
dirOptions.level = Number(dirOptions.level || 0)
|
||||
|
||||
const present = isDirectory(dirPath)
|
||||
if (!present && dirOptions.path !== '~/components') {
|
||||
if (!present && basename(dirOptions.path) !== 'components') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Components directory not found: `' + dirPath + '`')
|
||||
}
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { basename, extname, join, dirname, relative } from 'pathe'
|
||||
import { globby } from 'globby'
|
||||
import { pascalCase, splitByCase } from 'scule'
|
||||
import type { ScanDir, 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
|
||||
}
|
||||
import type { Component, ComponentsDir } from '@nuxt/schema'
|
||||
|
||||
// vue@2 src/shared/util.js
|
||||
// TODO: update to vue3?
|
||||
function hyphenate (str: string):string {
|
||||
function hyphenate (str: string): string {
|
||||
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
|
||||
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)
|
||||
const resolvedNames = new Map<string, string>()
|
||||
|
||||
@ -112,7 +108,6 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
shortPath,
|
||||
export: 'default',
|
||||
global: dir.global,
|
||||
level: Number(dir.level),
|
||||
prefetch: Boolean(dir.prefetch),
|
||||
preload: Boolean(dir.preload)
|
||||
}
|
||||
@ -121,11 +116,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
component = (await dir.extendComponent(component)) || component
|
||||
}
|
||||
|
||||
// Check if component is already defined, used to overwite if level is inferiour
|
||||
const definedComponent = components.find(c => c.pascalName === component.pascalName)
|
||||
if (definedComponent && component.level < definedComponent.level) {
|
||||
Object.assign(definedComponent, component)
|
||||
} else if (!definedComponent) {
|
||||
// Ignore component if component is already defined
|
||||
if (!components.find(c => c.pascalName === component.pascalName)) {
|
||||
components.push(component)
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ const rFixture = (...p) => resolve(fixtureDir, ...p)
|
||||
const dirs: ComponentsDir[] = [
|
||||
{
|
||||
path: rFixture('components'),
|
||||
level: 0,
|
||||
enabled: true,
|
||||
extensions: [
|
||||
'vue'
|
||||
@ -24,7 +23,6 @@ const dirs: ComponentsDir[] = [
|
||||
},
|
||||
{
|
||||
path: rFixture('components'),
|
||||
level: 0,
|
||||
enabled: true,
|
||||
extensions: [
|
||||
'vue'
|
||||
@ -43,7 +41,6 @@ const dirs: ComponentsDir[] = [
|
||||
'vue'
|
||||
],
|
||||
prefix: 'nuxt',
|
||||
level: 0,
|
||||
enabled: true,
|
||||
pattern: '**/*.{vue,}',
|
||||
ignore: [
|
||||
@ -63,7 +60,6 @@ const expectedComponents = [
|
||||
shortPath: 'components/HelloWorld.vue',
|
||||
export: 'default',
|
||||
global: undefined,
|
||||
level: 0,
|
||||
prefetch: false,
|
||||
preload: false
|
||||
},
|
||||
@ -74,7 +70,6 @@ const expectedComponents = [
|
||||
shortPath: 'components/Nuxt3.vue',
|
||||
export: 'default',
|
||||
global: undefined,
|
||||
level: 0,
|
||||
prefetch: false,
|
||||
preload: false
|
||||
},
|
||||
@ -85,7 +80,6 @@ const expectedComponents = [
|
||||
shortPath: 'components/parent-folder/index.vue',
|
||||
export: 'default',
|
||||
global: undefined,
|
||||
level: 0,
|
||||
prefetch: false,
|
||||
preload: false
|
||||
}
|
||||
|
@ -12,17 +12,12 @@ export default {
|
||||
*/
|
||||
components: {
|
||||
$resolve: (val, get) => {
|
||||
if (!val) {
|
||||
// Nuxt 2 and Nuxt 3 have different default values when this option is not set,
|
||||
// so we defer to the module's own defaults here.
|
||||
return undefined
|
||||
if (Array.isArray(val)) {
|
||||
return { dirs: val }
|
||||
}
|
||||
if (val === undefined || val === true) {
|
||||
return { dirs: ['~/components'] }
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
return { dirs: val }
|
||||
}
|
||||
return val
|
||||
}
|
||||
},
|
||||
|
@ -5,11 +5,12 @@ export interface Component {
|
||||
filePath: string
|
||||
shortPath: string
|
||||
chunkName: string
|
||||
level: number
|
||||
prefetch: boolean
|
||||
preload: boolean
|
||||
global?: boolean
|
||||
|
||||
/** @deprecated */
|
||||
level?: number
|
||||
/** @deprecated */
|
||||
import?: string
|
||||
/** @deprecated */
|
||||
@ -46,6 +47,7 @@ export interface ScanDir {
|
||||
enabled?: boolean
|
||||
/**
|
||||
* 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
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { defineNuxtConfig } from 'nuxt3'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
extends: './base',
|
||||
modules: [
|
||||
'@nuxt/ui'
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user