From aa4bc3ed201e7db53b729658a68fbaa9eb1ec52d Mon Sep 17 00:00:00 2001 From: Kael Date: Mon, 12 Aug 2024 22:00:59 +1000 Subject: [PATCH] feat: add excludeNames option (#788) --- README.md | 8 ++++++-- src/core/context.ts | 6 +++++- src/core/options.ts | 2 +- src/core/resolvers/_utils.ts | 18 ------------------ src/core/resolvers/arco.ts | 3 +-- src/core/resolvers/layui-vue.ts | 14 ++++---------- src/core/resolvers/tdesign.ts | 22 ++++------------------ src/core/utils.ts | 20 ++++++++++++++++++++ src/types.ts | 7 ++++++- test/search.test.ts | 11 +++++++++++ 10 files changed, 58 insertions(+), 53 deletions(-) delete mode 100644 src/core/resolvers/_utils.ts diff --git a/README.md b/README.md index 298480f3..9a14ea9e 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ Components({ extensions: ['vue'], // Glob patterns to match file names to be detected as components. - // When specified, the `dirs` and `extensions` options will be ignored. + // When specified, the `dirs`, `extensions`, and `directoryAsNamespace` options will be ignored. // If you want to exclude components being registered, use negative globs with leading `!`. globs: ['src/components/*.{vue}'], @@ -398,10 +398,14 @@ Components({ allowOverrides: false, // Filters for transforming targets (components to insert the auto import) - // Note these are NOT about including/excluding components registered - use `globs` for that + // Note these are NOT about including/excluding components registered - use `globs` or `excludeNames` for that include: [/\.vue$/, /\.vue\?vue/], exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/], + // Filters for component names that will not be imported + // Use for globally imported async components or other conflicts that the plugin cannot detect + excludeNames: [/^Async.+/], + // Vue version of project. It will detect automatically if not specified. // Acceptable value: 2 | 2.7 | 3 version: 2.7, diff --git a/src/core/context.ts b/src/core/context.ts index c16ea096..480e164b 100644 --- a/src/core/context.ts +++ b/src/core/context.ts @@ -6,7 +6,7 @@ import type { UpdatePayload, ViteDevServer } from 'vite' import { slash, throttle, toArray } from '@antfu/utils' import type { ComponentInfo, Options, ResolvedOptions, Transformer } from '../types' import { DIRECTIVE_IMPORT_PREFIX } from './constants' -import { getNameFromFilePath, matchGlobs, normalizeComponentInfo, parseId, pascalCase, resolveAlias } from './utils' +import { getNameFromFilePath, isExclude, matchGlobs, normalizeComponentInfo, parseId, pascalCase, resolveAlias } from './utils' import { resolveOptions } from './options' import { searchComponents } from './fs/glob' import { writeDeclaration } from './declaration' @@ -203,6 +203,10 @@ export class Context { .from(this._componentPaths) .forEach((path) => { const name = pascalCase(getNameFromFilePath(path, this.options)) + if (isExclude(name, this.options.excludeNames)) { + debug.components('exclude', name) + return + } if (this._componentNameMap[name] && !this.options.allowOverrides) { console.warn(`[unplugin-vue-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`) return diff --git a/src/core/options.ts b/src/core/options.ts index 9a082e67..f05729c2 100644 --- a/src/core/options.ts +++ b/src/core/options.ts @@ -4,7 +4,7 @@ import { getPackageInfoSync, isPackageExists } from 'local-pkg' import type { ComponentResolver, ComponentResolverObject, Options, ResolvedOptions } from '../types' import { detectTypeImports } from './type-imports/detect' -export const defaultOptions: Omit, 'include' | 'exclude' | 'transformer' | 'globs' | 'directives' | 'types' | 'version'> = { +export const defaultOptions: Omit, 'include' | 'exclude' | 'excludeNames' | 'transformer' | 'globs' | 'directives' | 'types' | 'version'> = { dirs: 'src/components', extensions: 'vue', deep: true, diff --git a/src/core/resolvers/_utils.ts b/src/core/resolvers/_utils.ts deleted file mode 100644 index 3c907ac3..00000000 --- a/src/core/resolvers/_utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function isExclude(name: string, exclude?: string | RegExp | (string | RegExp)[] | undefined): boolean { - if (!exclude) - return false - - if (typeof exclude === 'string') - return name === exclude - - if (exclude instanceof RegExp) - return !!name.match(exclude) - - if (Array.isArray(exclude)) { - for (const item of exclude) { - if (name === item || name.match(item)) - return true - } - } - return false -} diff --git a/src/core/resolvers/arco.ts b/src/core/resolvers/arco.ts index c61cc653..8ade3fb7 100644 --- a/src/core/resolvers/arco.ts +++ b/src/core/resolvers/arco.ts @@ -1,7 +1,6 @@ import Debug from 'debug' import type { ComponentInfo, ComponentResolver } from '../../types' -import { kebabCase, pascalCase } from '../utils' -import { isExclude } from './_utils' +import { isExclude, kebabCase, pascalCase } from '../utils' const debug = Debug('unplugin-vue-components:resolvers:arco') diff --git a/src/core/resolvers/layui-vue.ts b/src/core/resolvers/layui-vue.ts index ea5024ec..afbf358a 100644 --- a/src/core/resolvers/layui-vue.ts +++ b/src/core/resolvers/layui-vue.ts @@ -1,3 +1,5 @@ +import type { FilterPattern } from '@rollup/pluginutils' +import { isExclude } from '../utils' import type { ComponentInfo, ComponentResolver, SideEffectsInfo } from '../../types' const matchComponents = [ @@ -95,7 +97,7 @@ export interface LayuiVueResolverOptions { * exclude components that do not require automatic import * */ - exclude?: Array + exclude?: FilterPattern } const layuiRE = /^Lay[A-Z]/ @@ -132,7 +134,7 @@ function getSideEffects(importName: string, options: LayuiVueResolverOptions): S function resolveComponent(importName: string, options: LayuiVueResolverOptions): ComponentInfo | undefined { let name: string | undefined - if (options.exclude && isExclude(importName, options.exclude)) + if (isExclude(importName, options.exclude)) return undefined if (options.resolveIcons && importName.match(iconsRE)) { @@ -152,14 +154,6 @@ function resolveComponent(importName: string, options: LayuiVueResolverOptions): : undefined } -function isExclude(name: string, exclude: Array): boolean { - for (const item of exclude) { - if (name === item || name.match(item)) - return true - } - return false -} - /** * Resolver for layui-vue * diff --git a/src/core/resolvers/tdesign.ts b/src/core/resolvers/tdesign.ts index 73a03962..7753b965 100644 --- a/src/core/resolvers/tdesign.ts +++ b/src/core/resolvers/tdesign.ts @@ -1,4 +1,6 @@ +import type { FilterPattern } from '@rollup/pluginutils' import type { ComponentResolver } from '../../types' +import { isExclude } from '../utils' export interface TDesignResolverOptions { /** @@ -23,7 +25,7 @@ export interface TDesignResolverOptions { * exclude component name, if match do not resolve the name * */ - exclude?: string | RegExp | (string | RegExp)[] + exclude?: FilterPattern } export function TDesignResolver(options: TDesignResolverOptions = {}): ComponentResolver { @@ -34,7 +36,7 @@ export function TDesignResolver(options: TDesignResolverOptions = {}): Component const { library = 'vue', exclude } = options const importFrom = options.esm ? '/esm' : '' - if (options.exclude && isExclude(name, exclude)) + if (isExclude(name, exclude)) return if (options.resolveIcons && name.match(/[a-z]Icon$/)) { @@ -55,19 +57,3 @@ export function TDesignResolver(options: TDesignResolverOptions = {}): Component }, } } - -function isExclude(name: string, exclude: string | RegExp | (string | RegExp)[] | undefined): boolean { - if (typeof exclude === 'string') - return name === exclude - - if (exclude instanceof RegExp) - return !!name.match(exclude) - - if (Array.isArray(exclude)) { - for (const item of exclude) { - if (name === item || name.match(item)) - return true - } - } - return false -} diff --git a/src/core/utils.ts b/src/core/utils.ts index df1084a2..ab57c665 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -6,6 +6,7 @@ import { getPackageInfo, isPackageExists, } from 'local-pkg' +import type { FilterPattern } from '@rollup/pluginutils' import type { ComponentInfo, ImportInfo, ImportInfoLegacy, Options, ResolvedOptions } from '../types' import type { Context } from './context' import { DISABLE_COMMENT } from './constants' @@ -222,3 +223,22 @@ export function shouldTransform(code: string) { return false return true } + +export function isExclude(name: string, exclude?: FilterPattern): boolean { + if (!exclude) + return false + + if (typeof exclude === 'string') + return name === exclude + + if (exclude instanceof RegExp) + return !!name.match(exclude) + + if (Array.isArray(exclude)) { + for (const item of exclude) { + if (name === item || name.match(item)) + return true + } + } + return false +} diff --git a/src/types.ts b/src/types.ts index 0c839cc8..967d9486 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,6 +71,11 @@ export interface Options { */ exclude?: FilterPattern + /** + * RegExp or string to match component names that will NOT be imported + */ + excludeNames?: FilterPattern + /** * Relative paths to the directory to search for components. * @default 'src/components' @@ -86,7 +91,7 @@ export interface Options { /** * Glob patterns to match file names to be detected as components. * - * When specified, the `dirs` and `extensions` options will be ignored. + * When specified, the `dirs`, `extensions`, and `directoryAsNamespace` options will be ignored. */ globs?: string | string[] diff --git a/test/search.test.ts b/test/search.test.ts index 594a31db..e48bec4d 100644 --- a/test/search.test.ts +++ b/test/search.test.ts @@ -69,4 +69,15 @@ describe('search', () => { expect(cleanup(ctx.componentNameMap).map(i => i.as)).not.toEqual(expect.arrayContaining(['Book'])) }) + + it('should excludeNames', () => { + const ctx = new Context({ + dirs: ['src/components'], + excludeNames: ['Book'], + }) + ctx.setRoot(root) + ctx.searchGlob() + + expect(cleanup(ctx.componentNameMap).map(i => i.as)).not.toEqual(expect.arrayContaining(['Book'])) + }) })