feat!(nuxt3): extends support for components/ directory (#3108)

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
This commit is contained in:
pooya parsa 2022-02-07 21:48:25 +01:00 committed by GitHub
parent c9c0171b2d
commit 790a54897a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 72 additions and 32 deletions

View File

@ -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>

View File

@ -0,0 +1,5 @@
<template>
<button role="button">
<slot />
</button>
</template>

View File

@ -0,0 +1,5 @@
<template>
<BaseButton>
<slot />
</BaseButton>
</template>

View 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>

View File

@ -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 + '`')
} }

View File

@ -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)
} }
} }

View File

@ -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
} }

View File

@ -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
} }
}, },

View File

@ -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
/** /**

View File

@ -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'
] ]