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">
theme runtimeConfig
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
<BaseButton>Base Button</BaseButton>
<FancyButton>Fancy Button</FancyButton>
</NuxtExampleLayout>
</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 { 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 + '`')
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
extends: './base',
modules: [
'@nuxt/ui'
]