This repository has been archived by the owner on Aug 28, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
277 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import type { Alias, ConfigEnv, Plugin, UserConfig } from 'vite'; | ||
import fs from 'fs' | ||
import path from 'path' | ||
import { globSync } from 'glob' | ||
import { TreeNode } from '@vben/utils'; | ||
import { split, slice, indexOf, isEmpty, find } from 'lodash-es'; | ||
import { bold, cyan, gray, green } from 'picocolors'; | ||
|
||
export type Options = Omit<Alias, 'customResolver'> | ||
/** | ||
* 创建别名解析规则 | ||
* | ||
* @param config | ||
* @param env | ||
* @param options | ||
* @returns | ||
*/ | ||
export const createAlias = (config: UserConfig, env: ConfigEnv, options: Options): Alias => { | ||
const componentCache = new TreeNode<string>(); | ||
return { | ||
...options, | ||
customResolver(updatedId, importerId, resolveOptions) { | ||
const pathSegs = split(importerId, path.sep).filter(p => p !== '') | ||
const paths = slice(pathSegs, 0, indexOf(pathSegs, 'src')) | ||
const cacheNode = componentCache.findByPath([...paths], true); | ||
if (cacheNode) { | ||
const pkgBasePath = path.resolve(path.sep, path.join(...[...cacheNode.treePath, cacheNode.path])) | ||
const pkgPath = path.join(pkgBasePath, 'package.json') | ||
if (!fs.existsSync(pkgPath)) { | ||
throw new Error(`MonoRepoResolverPlugin can not resolve Module at: ${pkgBasePath}`) | ||
} | ||
// 分割别名对应的相对路径路径。代码实际导入的时候都会使用'/',不需要使用Path.seg | ||
const relPaths = slice(split(updatedId, '/').filter(p => p !== '')) | ||
// 从缓存的包路径节点继续向下查找对应的组件路径 | ||
const pkgNode = cacheNode.findByPath(relPaths, true); | ||
if (pkgNode) { | ||
if (isEmpty(pkgNode.val)) { | ||
// 如果缓存的路径节点没有值,说明还没有解析过,需要解析 | ||
const parsedComPath = path.join(...[pkgBasePath, ...relPaths]) | ||
let realPath | ||
if (fs.existsSync(parsedComPath)) { | ||
// import路径存在,确定是文件还是文件夹,分别处理 | ||
if (fs.statSync(parsedComPath).isDirectory()) { | ||
// 如果导入的是文件夹,文件加载应该有index.xxx的入口文件 | ||
const components = globSync(`${parsedComPath}${path.sep}index.*`) | ||
if (components.length === 1) { | ||
realPath = components[0] | ||
} else { | ||
const fileTsOrJs = find(components, (c) => c.endsWith('.ts') || c.endsWith('.js')) | ||
if (fileTsOrJs) { | ||
realPath = fileTsOrJs | ||
} else { | ||
throw new Error( | ||
`MonoRepoResolverPlugin can not resolve Module <${updatedId}> at: ${importerId}, find ${components.length === 0 ? 'none' : 'multiple' | ||
} files at: ${parsedComPath}, please check it. components: ${components}` | ||
); | ||
} | ||
} | ||
} else { | ||
// 如果导入的是文件,直接使用 | ||
realPath = parsedComPath | ||
} | ||
} else { | ||
// import文件不存在,需要进一步处理,尝试直接搜索相关文件 | ||
const components = globSync(`${parsedComPath}.*`) | ||
if (components.length === 1) { | ||
realPath = components[0] | ||
} else { | ||
throw new Error( | ||
`MonoRepoResolverPlugin can not resolve Module <${updatedId}> at: ${importerId}, find ${components.length === 0 ? 'none' : 'multiple' | ||
} files at: ${parsedComPath}, please check it. components: ${components}` | ||
); | ||
} | ||
} | ||
pkgNode.val = realPath | ||
console.debug(`${bold(cyan('[MonoRepoResolverPlugin]'))} ${green(`resolve Component from "${updatedId}" to ${pkgNode?.val} at:`)} ${gray(importerId)}`); | ||
} | ||
return pkgNode.val; | ||
} else { | ||
throw new Error(`MonoRepoResolverPlugin can not resolve Module <${updatedId}> at: ${importerId}, cache module subtree is empty`) | ||
} | ||
} else { | ||
throw new Error(`MonoRepoResolverPlugin can not resolve Module at: ${importerId}, cache module tree is empty`) | ||
} | ||
}, | ||
} | ||
} | ||
/** | ||
* 导出Vite插件 | ||
* | ||
* @param rawOptions | ||
* @returns | ||
*/ | ||
export default function configMonoRepoResolverPlugin( | ||
rawOptions: Options = { | ||
find: '#', | ||
replacement: 'src' | ||
} | ||
): Plugin { | ||
return { | ||
name: 'MonoRepoResolver', | ||
config: (config, env) => ({ | ||
resolve: { | ||
alias: [ | ||
createAlias(config, env, rawOptions), | ||
], | ||
}, | ||
}), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { expect, test, describe } from 'vitest'; | ||
import path from 'path'; | ||
import { createAlias } from '../../src/plugins/monorepo'; | ||
import { ResolverFunction } from 'vite'; | ||
|
||
const TEST_TITLE = '【测试MonoRepoResolverPlugin】' | ||
const buildTestPkgPath = (pkgRoot: string = process.cwd()) => { | ||
const demoPkgPath = path.resolve(pkgRoot, './configs/vite/test/plugins/testpkg') | ||
return [ | ||
demoPkgPath, | ||
path.resolve(demoPkgPath, './index.ts') | ||
] | ||
} | ||
|
||
describe('【测试MonoRepoResolverPlugin】', () => { | ||
test(`正常创建alias`, async () => { | ||
const aliasConfig = createAlias({}, { command: 'serve', mode: 'test' }, { | ||
find: '#', | ||
replacement: 'src' | ||
}); | ||
expect(aliasConfig).toBeTruthy() | ||
expect(aliasConfig.customResolver).toBeTypeOf('function') | ||
}) | ||
|
||
const aliasConfig = createAlias({}, { command: 'serve', mode: 'test' }, { | ||
find: '#', | ||
replacement: 'src' | ||
}); | ||
const customResolver = aliasConfig.customResolver as ResolverFunction | ||
|
||
describe('测试customResolver解析组件路径', () => { | ||
test(`正常解析testpkg/src/test.ts`, async () => { | ||
const [, importerId] = buildTestPkgPath() | ||
expect(customResolver.call(null, 'src/test', importerId, null)).contains('test/plugins/testpkg/src/test.ts') | ||
}) | ||
|
||
test(`找不到组件`, async () => { | ||
const [, importerId] = buildTestPkgPath() | ||
expect(() => customResolver.call(null, 'src/notfound', importerId)).toThrowError(/find none files at/) | ||
}) | ||
|
||
test(`组件中存在index.ts和index.vue, 解析到index.ts`, async () => { | ||
const [, importerId] = buildTestPkgPath() | ||
expect(customResolver.call(null, 'src/vue&ts', importerId)).contains('testpkg/src/vue&ts/index.ts') | ||
}) | ||
}) | ||
}); |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "monorepo-test", | ||
"version": "0.0.0" | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const aa = { test: true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<template> | ||
<div></div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export * from './tree/index'; | ||
/** | ||
* 用于存储固定类型的数据字典 | ||
* | ||
* 比如:DataDictionary<Component>,用于存储组件清单 | ||
*/ | ||
export type DataDictionary<T> = { | ||
[key: string]: T; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { find, isEmpty } from 'lodash-es'; | ||
|
||
// 多叉树的类型定义 | ||
export class TreeNode<T> { | ||
// 节点值 | ||
val?: T; | ||
// 节点路径 | ||
path: string; | ||
// 树路径 | ||
treePath: string[]; | ||
// 子节点 | ||
children: TreeNode<T>[]; | ||
constructor(path?: string, val?: T, parentNode?:TreeNode<T>) { | ||
this.val = val; | ||
this.path = path || ''; | ||
this.children = []; | ||
if (parentNode) { | ||
if (parentNode.isRoot()) { | ||
this.treePath = []; | ||
} else { | ||
this.treePath = [...parentNode.treePath, parentNode.path]; | ||
} | ||
} else { | ||
this.treePath = [] | ||
} | ||
} | ||
|
||
// 是否是根节点 | ||
isRoot(): boolean { | ||
return this.treePath.length === 0 && this.path === ''; | ||
} | ||
|
||
// 添加子节点 | ||
addChild(child: TreeNode<T>) { | ||
this.children.push(child); | ||
} | ||
// 删除子节点 | ||
removeChild(child: TreeNode<T>) { | ||
const index = this.children.indexOf(child); | ||
if (index > -1) { | ||
this.children.splice(index, 1); | ||
} | ||
} | ||
// 是否是目标节点 | ||
isNodeValEqual(val: T): boolean { | ||
return this.val === val; | ||
} | ||
|
||
// 深度优先查找 | ||
dfs(val: T): TreeNode<T> | null { | ||
if (this.isNodeValEqual(val)) { | ||
return this; | ||
} | ||
for (let i = 0; i < this.children.length; i++) { | ||
const node = this.children[i].dfs(val); | ||
if (node) { | ||
return node; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* 按路径查询 | ||
* @param paths 路径数组 | ||
* @param createWhenNotFound 路径数组 | ||
* @returns | ||
*/ | ||
findByPath(paths: string[], createWhenNotFound = false, _parentNode?:TreeNode<T>): TreeNode<T> | null { | ||
for (let i = 0; i < paths.length; i++) { | ||
const [currentPath, ...nextPaths] = paths; | ||
const child = find(this.children, (node) => node.path === currentPath); | ||
if (child) { | ||
if (isEmpty(nextPaths)) { | ||
return child; | ||
} else { | ||
return child.findByPath(nextPaths, createWhenNotFound); | ||
} | ||
} else if (createWhenNotFound) { | ||
const childNode = new TreeNode<T>(currentPath, undefined, this); | ||
this.addChild(childNode); | ||
if (isEmpty(nextPaths)) { | ||
return childNode; | ||
} else { | ||
return childNode.findByPath(nextPaths, createWhenNotFound); | ||
} | ||
} else { | ||
return null; | ||
} | ||
} | ||
return null; | ||
} | ||
} |