Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
feat!(nuxt3): extends support for components/ directory (#3108)
Browse files Browse the repository at this point in the history
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
  • Loading branch information
pi0 and atinux authored Feb 7, 2022
1 parent c9c0171 commit 790a548
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 32 deletions.
2 changes: 2 additions & 0 deletions examples/config-extends/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
5 changes: 5 additions & 0 deletions examples/config-extends/base/components/BaseButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<button role="button">
<slot />
</button>
</template>
5 changes: 5 additions & 0 deletions examples/config-extends/base/components/FancyButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<BaseButton>
<slot />
</BaseButton>
</template>
16 changes: 16 additions & 0 deletions examples/config-extends/components/FancyButton.vue
Original file line number Diff line number Diff line change
@@ -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>
38 changes: 33 additions & 5 deletions packages/nuxt3/src/components/module.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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: {
Expand All @@ -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()
]

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'
Expand All @@ -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 + '`')
}
Expand Down
18 changes: 5 additions & 13 deletions packages/nuxt3/src/components/scan.ts
Original file line number Diff line number Diff line change
@@ -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()
}

Expand All @@ -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>()

Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
}
Expand Down
6 changes: 0 additions & 6 deletions packages/nuxt3/test/scan-components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const rFixture = (...p) => resolve(fixtureDir, ...p)
const dirs: ComponentsDir[] = [
{
path: rFixture('components'),
level: 0,
enabled: true,
extensions: [
'vue'
Expand All @@ -24,7 +23,6 @@ const dirs: ComponentsDir[] = [
},
{
path: rFixture('components'),
level: 0,
enabled: true,
extensions: [
'vue'
Expand All @@ -43,7 +41,6 @@ const dirs: ComponentsDir[] = [
'vue'
],
prefix: 'nuxt',
level: 0,
enabled: true,
pattern: '**/*.{vue,}',
ignore: [
Expand All @@ -63,7 +60,6 @@ const expectedComponents = [
shortPath: 'components/HelloWorld.vue',
export: 'default',
global: undefined,
level: 0,
prefetch: false,
preload: false
},
Expand All @@ -74,7 +70,6 @@ const expectedComponents = [
shortPath: 'components/Nuxt3.vue',
export: 'default',
global: undefined,
level: 0,
prefetch: false,
preload: false
},
Expand All @@ -85,7 +80,6 @@ const expectedComponents = [
shortPath: 'components/parent-folder/index.vue',
export: 'default',
global: undefined,
level: 0,
prefetch: false,
preload: false
}
Expand Down
9 changes: 2 additions & 7 deletions packages/schema/src/config/_adhoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
Expand Down
4 changes: 3 additions & 1 deletion packages/schema/src/types/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
/**
Expand Down
1 change: 1 addition & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
extends: './base',
modules: [
'@nuxt/ui'
]
Expand Down

0 comments on commit 790a548

Please sign in to comment.