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
6 changed files
with
315 additions
and
67 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,153 @@ | ||
import { dirname, isAbsolute, relative } from 'path' | ||
import { existsSync, promises as fs } from 'fs' | ||
import { existsSync } from 'fs' | ||
import { readFile, writeFile } from 'fs/promises' | ||
import { notNullish, slash } from '@antfu/utils' | ||
import type { ComponentInfo } from '../../dist' | ||
import type { Options } from '../types' | ||
import type { Context } from './context' | ||
import { getTransformedPath } from './utils' | ||
import { resolveTypeImports } from './type-imports/detect' | ||
|
||
export function parseDeclaration(code: string): Record<string, string> { | ||
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 {} | ||
return Object.fromEntries(Array.from(code.matchAll(/(?<!\/\/)\s+\s+['"]?(.+?)['"]?:\s(.+?)\n/g)).map(i => [i[1], i[2]])) | ||
return | ||
|
||
code = code | ||
.replace(multilineCommentsRE, '') | ||
.replace(singlelineCommentsRE, '') | ||
|
||
const imports: DeclarationImports = { | ||
component: {}, | ||
directive: {}, | ||
} | ||
const componentDeclaration = /export\s+interface\s+GlobalComponents\s*{(.*?)}/s.exec(code)?.[0] | ||
if (componentDeclaration) | ||
imports.component = extractImports(componentDeclaration) | ||
|
||
const directiveDeclaration = /export\s+interface\s+ComponentCustomProperties\s*{(.*?)}/s.exec(code)?.[0] | ||
if (directiveDeclaration) | ||
imports.directive = extractImports(directiveDeclaration) | ||
|
||
return imports | ||
} | ||
|
||
/** | ||
* Converts `ComponentInfo` to an array | ||
* | ||
* `[name, "typeof import(path)[importName]"]` | ||
*/ | ||
function stringifyComponentInfo(filepath: string, { from: path, as: name, name: importName }: ComponentInfo, importPathTransform?: Options['importPathTransform']): [string, string] | undefined { | ||
if (!name) | ||
return undefined | ||
path = getTransformedPath(path, importPathTransform) | ||
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[], importPathTransform?: Options['importPathTransform']): Record<string, string> { | ||
return Object.fromEntries( | ||
components.map(info => stringifyComponentInfo(filepath, info, importPathTransform)) | ||
.filter(notNullish), | ||
) | ||
} | ||
|
||
export async function generateDeclaration(ctx: Context, root: string, filepath: string, removeUnused = false): Promise<void> { | ||
const items = [ | ||
export interface DeclarationImports { | ||
component: Record<string, string> | ||
directive: Record<string, string> | ||
} | ||
|
||
export function getDeclarationImports(ctx: Context, filepath: string): DeclarationImports | undefined { | ||
const component = stringifyComponentsInfo(filepath, [ | ||
...Object.values({ | ||
...ctx.componentNameMap, | ||
...ctx.componentCustomMap, | ||
}), | ||
...resolveTypeImports(ctx.options.types), | ||
] | ||
const imports: Record<string, string> = Object.fromEntries( | ||
items.map(({ from: path, as: name, name: importName }) => { | ||
if (!name) | ||
return undefined | ||
path = getTransformedPath(path, ctx) | ||
const related = isAbsolute(path) | ||
? `./${relative(dirname(filepath), path)}` | ||
: path | ||
|
||
let entry = `typeof import('${slash(related)}')` | ||
if (importName) | ||
entry += `['${importName}']` | ||
else | ||
entry += '[\'default\']' | ||
return [name, entry] | ||
}) | ||
.filter(notNullish), | ||
], ctx.options.importPathTransform) | ||
|
||
const directive = stringifyComponentsInfo( | ||
filepath, | ||
Object.values(ctx.directiveCustomMap), | ||
ctx.options.importPathTransform, | ||
) | ||
|
||
if (!Object.keys(imports).length) | ||
if ( | ||
(Object.keys(component).length + Object.keys(directive).length) === 0 | ||
) | ||
return | ||
|
||
const originalContent = existsSync(filepath) ? await fs.readFile(filepath, 'utf-8') : '' | ||
|
||
const originalImports = parseDeclaration(originalContent) | ||
return { component, directive } | ||
} | ||
|
||
const lines = Object.entries({ | ||
...originalImports, | ||
...imports, | ||
}) | ||
.sort((a, b) => a[0].localeCompare(b[0])) | ||
.filter(([name]) => removeUnused ? items.find(i => i.as === name) : true) | ||
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: Context, filepath: string, originalImports?: DeclarationImports) { | ||
const imports = getDeclarationImports(ctx, filepath) | ||
if (!imports) | ||
return | ||
|
||
const declarations = { | ||
component: stringifyDeclarationImports({ ...originalImports?.component, ...imports.component }), | ||
directive: stringifyDeclarationImports({ ...originalImports?.directive, ...imports.directive }), | ||
} | ||
|
||
const code = `// generated by unplugin-vue-components | ||
let code = `// generated by unplugin-vue-components | ||
// We suggest you to commit this file into source control | ||
// Read more: https://github.com/vuejs/core/pull/3399 | ||
import '@vue/runtime-core' | ||
declare module '@vue/runtime-core' { | ||
export {} | ||
declare module '@vue/runtime-core' {` | ||
|
||
if (Object.keys(declarations.component).length > 0) { | ||
code += ` | ||
export interface GlobalComponents { | ||
${lines.join('\n ')} | ||
${declarations.component.join('\n ')} | ||
} | ||
` | ||
} | ||
if (Object.keys(declarations.directive).length > 0) { | ||
code += ` | ||
export interface ComponentCustomProperties { | ||
${declarations.directive.join('\n ')} | ||
}` | ||
} | ||
code += '\n}\n' | ||
return code | ||
} | ||
|
||
export {} | ||
` | ||
export async function writeDeclaration(ctx: Context, 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 fs.writeFile(filepath, code, 'utf-8') | ||
await writeFile(filepath, code, 'utf-8') | ||
} |
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
Oops, something went wrong.