提交 80335c9e 编写于 作者: fxy060608's avatar fxy060608

chore: add uni-cli-utils

上级 259bb854
......@@ -48,7 +48,7 @@ module.exports = {
'jest.config.js',
'rollup.config.mjs',
'scripts/**',
'packages/{uni-cli-shared,uni-app-vite,uni-h5-vite,uni-mp-vite,uni-mp-compiler,vite-plugin-uni,uts,uni-uts-v1,uni-app-uts}/**',
'packages/{uni-cli-shared,uni-cli-utils,uni-app-vite,uni-h5-vite,uni-mp-vite,uni-mp-compiler,vite-plugin-uni,uts,uni-uts-v1,uni-app-uts}/**',
'packages/*/vite.config.ts',
],
rules: {
......
......@@ -10,6 +10,7 @@ packages/vite-plugin-uni/dist
packages/uts/dist
packages/uni-uts-v1/dist
packages/uni-app-uts/dist
packages/uni-cli-utils/dist
.DS_Store
......
import { resolve } from 'node:path'
import { EasyComContext } from '../src'
import { parseEasyComTag, parseGlob } from '../src/easycom/options'
const inputDir = resolve(__dirname, 'examples/easycom')
const outputDir = resolve(__dirname, 'examples/easycom/unpackage/dist/dev/app')
describe('easycom', () => {
test('parseGlob', () => {
expect(
parseGlob('@dcloudio/uni-ui/lib/uni-*/uni-*.vue', inputDir)
).toContain('/easycom/node_modules/@dcloudio/uni-ui/lib/uni-*/uni-*.vue')
})
test('parseEasyComTag', () => {
const easyComTag = parseEasyComTag(
'^uni-(.*)',
'@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
inputDir
)
expect(easyComTag).toMatchObject({
tag: '^uni-(.*)',
path: '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
})
expect(easyComTag.glob).toContain(
`/easycom/node_modules/@dcloudio/uni-ui/lib/uni-*/uni-*.vue`
)
expect(
easyComTag.parseTag(
`/easycom/node_modules/@dcloudio/uni-ui/lib/uni-badge/uni-badge.vue`
)
).toBe('uni-badge')
})
test('searchComponents', () => {
const ctx = new EasyComContext({
inputDir,
outputDir,
dts: resolve(inputDir, 'easycom.d.ts'),
})
ctx.searchComponents()
expect(ctx.componentMap['test']).not.toBeUndefined()
expect(ctx.componentMap['uni-badge']).not.toBeUndefined()
ctx.generateDeclaration()
})
})
<template></template>
<script lang="ts">
export default {
props: {
title: {
type: String,
default: '',
required: true
}
},
data() {
return {
msg: 'hello'
}
}
}
</script>
\ No newline at end of file
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
export {}
declare module 'vue' {
export interface GlobalComponents {
test: typeof import('./components/test/test.uvue')['default']
'test-a': typeof import('./components/test-a/test-a.vue')['default']
'test-b': typeof import('./uni_modules/test-easycom/components/test-b/test-b.vue')['default']
'uni-badge': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-badge/uni-badge.vue')['default']
'uni-breadcrumb': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-breadcrumb/uni-breadcrumb.vue')['default']
'uni-breadcrumb-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-breadcrumb-item/uni-breadcrumb-item.vue')['default']
'uni-calendar': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-calendar/uni-calendar.vue')['default']
'uni-calendar-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-calendar/uni-calendar-item.vue')['default']
'uni-card': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-card/uni-card.vue')['default']
'uni-col': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-col/uni-col.vue')['default']
'uni-collapse': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-collapse/uni-collapse.vue')['default']
'uni-collapse-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-collapse-item/uni-collapse-item.vue')['default']
'uni-combox': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-combox/uni-combox.vue')['default']
'uni-countdown': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-countdown/uni-countdown.vue')['default']
'uni-data-checkbox': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-data-checkbox/uni-data-checkbox.vue')['default']
'uni-data-picker': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-data-picker/uni-data-picker.vue')['default']
'uni-data-pickerview': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-data-pickerview/uni-data-pickerview.vue')['default']
'uni-data-select': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-data-select/uni-data-select.vue')['default']
'uni-dateformat': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-dateformat/uni-dateformat.vue')['default']
'uni-datetime-picker': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-datetime-picker/uni-datetime-picker.vue')['default']
'uni-drawer': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-drawer/uni-drawer.vue')['default']
'uni-easyinput': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-easyinput/uni-easyinput.vue')['default']
'uni-fab': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-fab/uni-fab.vue')['default']
'uni-fav': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-fav/uni-fav.vue')['default']
'uni-file-picker': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-file-picker/uni-file-picker.vue')['default']
'uni-forms': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-forms/uni-forms.vue')['default']
'uni-forms-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-forms-item/uni-forms-item.vue')['default']
'uni-goods-nav': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-goods-nav/uni-goods-nav.vue')['default']
'uni-grid': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-grid/uni-grid.vue')['default']
'uni-grid-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-grid-item/uni-grid-item.vue')['default']
'uni-group': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-group/uni-group.vue')['default']
'uni-icons': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue')['default']
'uni-indexed-list': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-indexed-list/uni-indexed-list.vue')['default']
'uni-indexed-list-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-indexed-list/uni-indexed-list-item.vue')['default']
'uni-link': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-link/uni-link.vue')['default']
'uni-list': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-list/uni-list.vue')['default']
'uni-list-ad': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-list-ad/uni-list-ad.vue')['default']
'uni-list-chat': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-list-chat/uni-list-chat.vue')['default']
'uni-list-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-list-item/uni-list-item.vue')['default']
'uni-load-more': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-load-more/uni-load-more.vue')['default']
'uni-nav-bar': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-nav-bar/uni-nav-bar.vue')['default']
'uni-notice-bar': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-notice-bar/uni-notice-bar.vue')['default']
'uni-number-box': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-number-box/uni-number-box.vue')['default']
'uni-pagination': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-pagination/uni-pagination.vue')['default']
'uni-popup': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-popup/uni-popup.vue')['default']
'uni-popup-dialog': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-popup-dialog/uni-popup-dialog.vue')['default']
'uni-popup-message': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-popup-message/uni-popup-message.vue')['default']
'uni-popup-share': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-popup-share/uni-popup-share.vue')['default']
'uni-rate': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-rate/uni-rate.vue')['default']
'uni-refresh': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-list/uni-refresh.vue')['default']
'uni-row': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-row/uni-row.vue')['default']
'uni-search-bar': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-search-bar/uni-search-bar.vue')['default']
'uni-section': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-section/uni-section.vue')['default']
'uni-segmented-control': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-segmented-control/uni-segmented-control.vue')['default']
'uni-status-bar': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-nav-bar/uni-status-bar.vue')['default']
'uni-steps': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-steps/uni-steps.vue')['default']
'uni-swipe-action': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-swipe-action/uni-swipe-action.vue')['default']
'uni-swipe-action-item': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-swipe-action-item/uni-swipe-action-item.vue')['default']
'uni-swiper-dot': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-swiper-dot/uni-swiper-dot.vue')['default']
'uni-table': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-table/uni-table.vue')['default']
'uni-tag': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-tag/uni-tag.vue')['default']
'uni-tbody': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-tbody/uni-tbody.vue')['default']
'uni-td': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-td/uni-td.vue')['default']
'uni-th': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-th/uni-th.vue')['default']
'uni-thead': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-thead/uni-thead.vue')['default']
'uni-title': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-title/uni-title.vue')['default']
'uni-tooltip': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-tooltip/uni-tooltip.vue')['default']
'uni-tr': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-tr/uni-tr.vue')['default']
'uni-transition': typeof import('./node_modules/@dcloudio/uni-ui/lib/uni-transition/uni-transition.vue')['default']
}
}
{
"name": "easycom",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@dcloudio/uni-ui": "^1.4.28"
}
}
{
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
}
}
<template>
<test-a :title="1" />
</template>
<script lang="ts"></script>
\ No newline at end of file
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2017",
"module": "esnext",
"lib": ["esnext", "DOM"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"jsx": "preserve"
},
"include": ["easycom.d.ts"]
}
{
"name": "@dcloudio/uni-cli-utils",
"version": "3.0.0-alpha-3090620231030001",
"description": "@dcloudio/uni-cli-utils",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"lib"
],
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dcloudio/uni-app.git",
"directory": "packages/uni-cli-utils"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/dcloudio/uni-app/issues"
},
"dependencies": {
"debug": "^4.3.3",
"fast-glob": "^3.2.11",
"jsonc-parser": "^3.2.0",
"minimatch": "^9.0.3",
"resolve": "^1.22.1",
"throttle-debounce": "^5.0.0"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/resolve": "^1.20.2",
"@types/throttle-debounce": "^5.0.2"
}
}
import { throttle } from 'throttle-debounce'
import type fs from 'node:fs'
import Debug from 'debug'
import fg from 'fast-glob'
import type { ComponentInfo, Options, ResolvedOptions } from './types'
import { writeDeclaration } from './declaration'
import { resolveOptions } from './options'
import { matchGlobs, parseTag, removeExtname } from './utils'
import { slash, toArray } from '../utils'
const debug = {
components: Debug('easycom:context:components'),
search: Debug('easycom:context:search'),
declaration: Debug('easycom:declaration'),
}
export class EasyComContext {
options: ResolvedOptions
private _componentPaths = new Map<string, string>()
private _componentMap: Record<string, ComponentInfo> = {}
constructor(rawOptions: Options) {
this.options = resolveOptions(rawOptions)
this.generateDeclaration = throttle(
500,
this._generateDeclaration.bind(this),
{ noLeading: false }
)
}
setupWatcher(watcher: fs.FSWatcher) {
const { globs, customGlobs } = this.options
watcher.on('unlink', (path) => {
if (matchGlobs(path, globs)) {
path = slash(path)
this.removeComponents(path)
this.onUpdate(path)
} else {
customGlobs.find((custom) => {
if (matchGlobs(path, [custom.glob])) {
path = slash(path)
this.removeComponents(path)
this.onUpdate(path)
return true
}
})
}
})
watcher.on('add', (path) => {
if (matchGlobs(path, globs)) {
path = slash(path)
this.addComponents(path, parseTag)
this.onUpdate(path)
} else {
customGlobs.find((custom) => {
if (matchGlobs(path, [custom.glob])) {
path = slash(path)
this.addComponents(path, custom.parseTag)
this.onUpdate(path)
return true
}
})
}
})
}
removeComponents(paths: string | string[]) {
debug.components('remove', paths)
const size = this._componentPaths.size
toArray(paths).forEach((p) => this._componentPaths.delete(p))
if (this._componentPaths.size !== size) {
this.updateComponentNameMap()
return true
}
return false
}
addComponents(
paths: string | string[],
parseTag: (filename: string) => string
) {
debug.components('add', paths)
const size = this._componentPaths.size
toArray(paths).forEach((p) => this._componentPaths.set(p, parseTag(p)))
if (this._componentPaths.size !== size) {
this.updateComponentNameMap()
return true
}
return false
}
onUpdate(_path: string) {
this.generateDeclaration()
}
private updateComponentNameMap() {
this._componentMap = {}
this._componentPaths.forEach((tag, path) => {
if (this._componentMap[tag]) {
if (
removeExtname(path) === removeExtname(this._componentMap[tag].from)
) {
// 仅后缀不同,如.uvue|.vue|.nvue
return
}
if (!this.options.allowOverrides) {
console.warn(
`[easycom] component "${tag}"(${path}) has naming conflicts with other components, ignored.`
)
return
}
}
this._componentMap[tag] = {
as: tag,
from: path,
}
})
}
_searched = false
searchGlob() {
if (this._searched) return
this.searchComponents()
debug.search(this._componentMap)
this._searched = true
}
searchComponents() {
debug.search(`started with: [${this.options.globs.join(', ')}]`)
const root = this.options.inputDir
const files = fg.sync(this.options.globs, {
ignore: ['node_modules', 'unpackage', 'uniCloud-*', 'static'],
onlyFiles: true,
cwd: root,
absolute: true,
})
debug.search(`${files.length} components found.`)
this.addComponents(files, parseTag)
this.searchCustomComponents()
}
searchCustomComponents() {
debug.search(`started with: [${this.options.customGlobs.join(', ')}]`)
const root = this.options.inputDir
this.options.customGlobs.forEach((custom) => {
const files = fg.sync(custom.glob, {
onlyFiles: true,
cwd: root,
absolute: true,
})
debug.search(`${files.length} components found.`)
this.addComponents(files, custom.parseTag)
})
}
_generateDeclaration(removeUnused = false) {
return writeDeclaration(this, this.options.dts, removeUnused)
}
generateDeclaration
get componentMap() {
return this._componentMap
}
}
import { dirname, isAbsolute, relative } from 'node:path'
import { existsSync } from 'node:fs'
import { mkdir, readFile, writeFile as writeFile_ } from 'node:fs/promises'
import type { EasyComContext } from './context'
import { notNullish, slash } from '../utils'
import { ComponentInfo } from './types'
const multilineCommentsRE = /\/\*.*?\*\//gms
const singlelineCommentsRE = /\/\/.*$/gm
function extractImports(code: string) {
return Object.fromEntries(
Array.from(code.matchAll(/['"]?([^\s'"]+)['"]?\s*:\s*(.+?)[,;\n]/g)).map(
(i) => [i[1], i[2]]
)
)
}
export function parseDeclaration(code: string): DeclarationImports | undefined {
if (!code) return
code = code.replace(multilineCommentsRE, '').replace(singlelineCommentsRE, '')
const imports: DeclarationImports = {
component: {},
}
const componentDeclaration =
/export\s+interface\s+GlobalComponents\s*{(.*?)}/s.exec(code)?.[0]
if (componentDeclaration)
imports.component = extractImports(componentDeclaration)
return imports
}
/**
* Converts `ComponentInfo` to an array
*
* `[name, "typeof import(path)[importName]"]`
*/
function stringifyComponentInfo(
filepath: string,
{ from: path, as: name, name: importName }: ComponentInfo
): [string, string] | undefined {
if (!name) return undefined
const related = isAbsolute(path)
? `./${relative(dirname(filepath), path)}`
: path
const entry = `typeof import('${slash(related)}')['${
importName || 'default'
}']`
return [name, entry]
}
/**
* Converts array of `ComponentInfo` to an import map
*
* `{ name: "typeof import(path)[importName]", ... }`
*/
export function stringifyComponentsInfo(
filepath: string,
components: ComponentInfo[]
): Record<string, string> {
return Object.fromEntries(
components
.map((info) => stringifyComponentInfo(filepath, info))
.filter(notNullish)
)
}
export interface DeclarationImports {
component: Record<string, string>
}
export function getDeclarationImports(
ctx: EasyComContext,
filepath: string
): DeclarationImports | undefined {
const component = stringifyComponentsInfo(filepath, [
...Object.values(ctx.componentMap),
])
if (Object.keys(component).length === 0) return
return { component }
}
export function stringifyDeclarationImports(imports: Record<string, string>) {
return Object.entries(imports)
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, v]) => {
if (!/^\w+$/.test(name)) name = `'${name}'`
return `${name}: ${v}`
})
}
export function getDeclaration(
ctx: EasyComContext,
filepath: string,
originalImports?: DeclarationImports
) {
const imports = getDeclarationImports(ctx, filepath)
if (!imports) return
const declarations = {
component: stringifyDeclarationImports({
...originalImports?.component,
...imports.component,
}),
}
const head = `export {}
declare module 'vue' {`
let code = `/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
${head}`
if (Object.keys(declarations.component).length > 0) {
code += `
export interface GlobalComponents {
${declarations.component.join('\n ')}
}`
}
code += '\n}\n'
return code
}
async function writeFile(filePath: string, content: string) {
await mkdir(dirname(filePath), { recursive: true })
return await writeFile_(filePath, content, 'utf-8')
}
export async function writeDeclaration(
ctx: EasyComContext,
filepath: string,
removeUnused = false
) {
const originalContent = existsSync(filepath)
? await readFile(filepath, 'utf-8')
: ''
const originalImports = removeUnused
? undefined
: parseDeclaration(originalContent)
const code = getDeclaration(ctx, filepath, originalImports)
if (!code) return
if (code !== originalContent) await writeFile(filepath, code)
}
export { EasyComContext } from './context'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { parse } from 'jsonc-parser'
import { sync } from 'resolve'
import type {
EasyComCustom,
EasyComOptions,
Options,
ResolvedOptions,
} from './types'
import { slash } from '../utils'
const AUTO_SCAN_GLOBS = [
'components/**/*.{vue,uvue,nvue}',
'uni_modules/*/components/**/*.{vue,uvue,nvue}',
]
function normalizeGlob(str: string, replace = '*') {
// 将所有 $0、$1 等占位符,替换为*
return str.replace(/\$\d+/g, replace)
}
export function resolveOptions(rawOptions: Options) {
const options: ResolvedOptions = {
dts: resolve(rawOptions.outputDir, '../.dts/easycom.d.ts'),
allowOverrides: false,
...rawOptions,
...initGlobs(rawOptions.inputDir),
}
return options
}
function initGlobs(inputDir: string) {
const customGlobs: EasyComCustom[] = []
let autoScan = true
const pagesJsonFilename = resolve(inputDir, 'pages.json')
if (existsSync(pagesJsonFilename)) {
const pagesJson = parse(readFileSync(pagesJsonFilename, 'utf8')) as {
easycom?: EasyComOptions
}
const easyComOptions = pagesJson?.easycom
if (easyComOptions) {
if (easyComOptions.autoscan === false) {
autoScan = false
}
const customOptions = easyComOptions.custom
if (customOptions) {
Object.keys(customOptions).forEach((tag) => {
customGlobs.push(parseEasyComTag(tag, customOptions[tag], inputDir))
})
}
}
}
return { globs: autoScan ? AUTO_SCAN_GLOBS : [], customGlobs }
}
export function parseGlob(glob: string, inputDir: string): string {
const parts = glob.split('/')
const index = parts.findIndex((part) => part.includes('*'))
let globParts: string[] = []
if (index > -1) {
globParts = parts.splice(index)
}
const popParts: string[] = []
function findPath(parts: string[]): string {
if (parts.length === 0) {
return glob
}
const checkPath = parts.join('/')
try {
const resolvedPath = sync(checkPath, {
basedir: inputDir,
packageFilter(pkg) {
if (!pkg.main) {
pkg.main = 'package.json'
}
return pkg
},
// preserveSymlinks: false,
})
return slash(join(dirname(resolvedPath), ...popParts, ...globParts))
} catch (err) {
const part = parts.pop()
if (part) {
popParts.push(part)
}
return findPath(parts)
}
}
return findPath(parts)
}
export function parseEasyComTag(
tag: string,
path: string,
inputDir: string
): EasyComCustom {
let glob = normalizeGlob(path)
// 项目根目录
if (glob.startsWith('@/')) {
glob = glob.slice(2)
} else {
// npm
glob = parseGlob(glob, inputDir)
}
const mappings: [number, number][] = []
const matches = path.match(/\$\d+/g)
if (matches) {
matches.forEach((m, index) => {
mappings.push([index + 1, parseInt(m.slice(1))])
})
}
/**
* 映射关系
* @dcloudio/uni-ui/lib/uni-$1/uni-$1.vue 会被转换为 @dcloudio/uni-ui/lib/uni-(.*)/uni-(.*).vue
* [[1, 1],[2, 1]]
* 第一个元素是转换后的(.*)索引位置,第二个元素是转换前的索引位置
*/
const regex = normalizeGlob(path, '(.*)')
return {
tag,
path,
glob,
parseTag(filename) {
const matches = filename.match(regex)
if (matches) {
let newTag = replaceWithIndexes(tag)
for (let i = mappings.length - 1; i >= 0; i--) {
const mapping = mappings[i]
newTag = newTag.replace('$' + mapping[1], matches[mapping[0]])
}
return newTag
}
return ''
},
}
}
function replaceWithIndexes(pattern: string) {
// 匹配(.*)的正则表达式
const regex = /\(\.\*\)/g
let index = 1
// 替换所有的(.*)为$+索引的形式
let replaced = pattern.replace(regex, function () {
return '$' + index++
})
if (replaced.startsWith('^')) {
replaced = replaced.slice(1)
}
if (replaced.endsWith('$')) {
replaced = replaced.slice(0, -1)
}
return replaced
}
export interface Options {
inputDir: string
outputDir: string
dts?: string
allowOverrides?: boolean
}
export interface ResolvedOptions extends Required<Options> {
globs: string[]
customGlobs: EasyComCustom[]
}
interface ImportInfo {
as?: string
name?: string
from: string
}
type SideEffectsInfo = (ImportInfo | string)[] | ImportInfo | string | undefined
export interface ComponentInfo extends ImportInfo {
sideEffects?: SideEffectsInfo
}
export interface EasyComOptions {
autoscan?: boolean
custom?: Record<string, string>
}
export interface EasyComCustom {
/**
* 组件标签名(正则)
* ^uni-(.*)
*/
tag: string
/**
* 组件路径
* @dcloudio/uni-ui/lib/uni-$1/uni-$1.vue
*/
path: string
/**
* 绝对路径的glob
*/
glob: string
/**
* 根据文件解析组件名
* @param filename
*/
parseTag(filename: string): string
}
import { minimatch } from 'minimatch'
import { slash } from '../utils'
import { basename } from 'node:path'
export function matchGlobs(filepath: string, globs: string[]) {
for (const glob of globs) {
if (minimatch(slash(filepath), glob)) return true
}
return false
}
export function parseTag(filename: string) {
return removeExtname(basename(filename))
}
export function removeExtname(filename: string) {
return filename.replace(/\.\w+$/, '')
}
export function notNullish<T>(v: T | null | undefined): v is NonNullable<T> {
return v != null
}
export function slash(str: string) {
return str.replace(/\\/g, '/')
}
export type Nullable<T> = T | null | undefined
export type Arrayable<T> = T | Array<T>
export function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T> {
array = array ?? []
return Array.isArray(array) ? array : [array]
}
export function pascalCase(str: string) {
return capitalize(camelCase(str))
}
export function camelCase(str: string) {
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}
export function kebabCase(key: string) {
const result = key.replace(/([A-Z])/g, ' $1').trim()
return result.split(' ').join('-').toLowerCase()
}
export function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
{
"extends": "../../tsconfig.node.json",
"compilerOptions": {
"baseUrl": "./",
"outDir": "dist",
"target": "ES2020",
"paths": {
"types/alias": ["types/alias.d.ts"]
}
},
"include": [
"src",
"types/shims.d.ts",
"../shims-node.d.ts",
"../shims-uni-app.d.ts"
]
}
packages:
- 'packages/*'
- 'packages/playground/*'
- 'packages/uni-cli-utils/__tests__/examples/easycom'
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册