From 790a54897a957c8a30f7f97015076a712dd352df Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Mon, 7 Feb 2022 21:48:25 +0100 Subject: [PATCH] feat!(nuxt3): extends support for `components/` directory (#3108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Chopin --- examples/config-extends/app.vue | 2 + .../base/components/BaseButton.vue | 5 +++ .../base/components/FancyButton.vue | 5 +++ .../config-extends/components/FancyButton.vue | 16 ++++++++ packages/nuxt3/src/components/module.ts | 38 ++++++++++++++++--- packages/nuxt3/src/components/scan.ts | 18 +++------ packages/nuxt3/test/scan-components.test.ts | 6 --- packages/schema/src/config/_adhoc.ts | 9 +---- packages/schema/src/types/components.ts | 4 +- playground/nuxt.config.ts | 1 + 10 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 examples/config-extends/base/components/BaseButton.vue create mode 100644 examples/config-extends/base/components/FancyButton.vue create mode 100644 examples/config-extends/components/FancyButton.vue diff --git a/examples/config-extends/app.vue b/examples/config-extends/app.vue index 430bf40a54e..82bc57217d8 100644 --- a/examples/config-extends/app.vue +++ b/examples/config-extends/app.vue @@ -6,6 +6,8 @@ const themeConfig = useRuntimeConfig().theme theme runtimeConfig
{{ JSON.stringify(themeConfig, null, 2) }}
+ Base Button + Fancy Button
diff --git a/examples/config-extends/base/components/BaseButton.vue b/examples/config-extends/base/components/BaseButton.vue new file mode 100644 index 00000000000..32799754620 --- /dev/null +++ b/examples/config-extends/base/components/BaseButton.vue @@ -0,0 +1,5 @@ + diff --git a/examples/config-extends/base/components/FancyButton.vue b/examples/config-extends/base/components/FancyButton.vue new file mode 100644 index 00000000000..97170c811ce --- /dev/null +++ b/examples/config-extends/base/components/FancyButton.vue @@ -0,0 +1,5 @@ + diff --git a/examples/config-extends/components/FancyButton.vue b/examples/config-extends/components/FancyButton.vue new file mode 100644 index 00000000000..3733ed04394 --- /dev/null +++ b/examples/config-extends/components/FancyButton.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/nuxt3/src/components/module.ts b/packages/nuxt3/src/components/module.ts index 7b6ccb75096..56a658d84bd 100644 --- a/packages/nuxt3/src/components/module.ts +++ b/packages/nuxt3/src/components/module.ts @@ -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({ meta: { @@ -17,15 +20,40 @@ export default defineNuxtModule({ 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() + ] + + await nuxt.callHook('components:dirs', allDirs) - componentDirs = options.dirs.filter(isPureObjectOrString).map((dir) => { + 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({ 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 + '`') } diff --git a/packages/nuxt3/src/components/scan.ts b/packages/nuxt3/src/components/scan.ts index fedd4a7ebbd..1cca1fc5f7a 100644 --- a/packages/nuxt3/src/components/scan.ts +++ b/packages/nuxt3/src/components/scan.ts @@ -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() @@ -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) } } diff --git a/packages/nuxt3/test/scan-components.test.ts b/packages/nuxt3/test/scan-components.test.ts index 59f4f23cbec..8a1b42c427b 100644 --- a/packages/nuxt3/test/scan-components.test.ts +++ b/packages/nuxt3/test/scan-components.test.ts @@ -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 } diff --git a/packages/schema/src/config/_adhoc.ts b/packages/schema/src/config/_adhoc.ts index 3535dd0c9fd..9816bd9c6a1 100644 --- a/packages/schema/src/config/_adhoc.ts +++ b/packages/schema/src/config/_adhoc.ts @@ -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 } }, diff --git a/packages/schema/src/types/components.ts b/packages/schema/src/types/components.ts index 7b53af63ce1..a0169ae192d 100644 --- a/packages/schema/src/types/components.ts +++ b/packages/schema/src/types/components.ts @@ -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 /** diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 9850816d150..000d20cd7a5 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -1,6 +1,7 @@ import { defineNuxtConfig } from 'nuxt3' export default defineNuxtConfig({ + extends: './base', modules: [ '@nuxt/ui' ]