Skip to content

feat(cli): 生成global.d.ts #1411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/devui-vue/devui-cli/commands/build-volar-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const path = require("path");
const {
buildComponentItem,
buildGlobalDTSEnd,
buildGlobalDTSStart,
buildComponents,
buildDirectiveItem,
buildDirective,
buildServiceItem,
buildService
} = require('../templates/dts');
const { writeFileSync } = require('fs');
const { useRelationTree } = require("../composables/use-relation-tree");
const { bigCamelCase } = require('../shared/utils');

/**
* @param {Record<string,any>} replaceIdentifier
* @param {string[]} readyToReleaseComponentName
*/
exports.volarSupport = (replaceIdentifier, readyToReleaseComponentName) => {
const componentDTSItem = [];
const directiveDTSItem = [];
const serviceDTSItem = [];
const componentPath = readyToReleaseComponentName.map((name) => path.resolve('./devui', name, 'index.ts'));
const tree = useRelationTree(componentPath);
tree.forEachChild((foldNode) => {
foldNode.forEachChild((node) => {
let nodeName = node.name.replace(/\$/gim, '').replace(/directive/gim, '');
let reference = nodeName;
const needToTransform = replaceIdentifier?.[foldNode.name]?.[node.name] !== undefined;
if (!node.isComponet){
const hasType = new RegExp(node.type, 'gim');
if (!hasType.test(reference)){
reference += `-${node.type}`;
}
reference = bigCamelCase(reference);
}
if (needToTransform){
reference = replaceIdentifier[foldNode.name][node.name]?.['reference'];
nodeName = replaceIdentifier[foldNode.name][node.name]?.['exportKey'];
}
if (node.type === 'component'){
componentDTSItem.push(buildComponentItem(bigCamelCase(nodeName), reference));
}
if (node.type === 'directive'){
directiveDTSItem.push(buildDirectiveItem(nodeName, reference));
}
if (node.type === 'service'){
serviceDTSItem.push(buildServiceItem(nodeName, reference));
}
});
});
const template = `
${buildGlobalDTSStart()}
${buildComponents(componentDTSItem.join('\n'))}
${buildDirective(directiveDTSItem.join('\n'))}
${buildService(serviceDTSItem.join('\n'))}
${buildGlobalDTSEnd()}
`;
try {
writeFileSync('./build/global.d.ts', template);
} catch (e) {
console.log(e.message);
return false;
}
return true;
};
29 changes: 25 additions & 4 deletions packages/devui-vue/devui-cli/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ const vue = require('@vitejs/plugin-vue');
const vueJsx = require('@vitejs/plugin-vue-jsx');
const nuxtBuild = require('./build-nuxt-auto-import');
const { isReadyToRelease } = require('../shared/utils');

const { execSync } = require('child_process');
const { volarSupport } = require('./build-volar-support');
const logger = require('../shared/logger');
const replaceIdentifierPath = path.resolve(__dirname,'../replaceIdentifer.json');
const replaceIdentifier = JSON.parse(fs.readFileSync(replaceIdentifierPath).toString());
const entryDir = path.resolve(__dirname, '../../devui');
const outputDir = path.resolve(__dirname, '../../build');

Expand Down Expand Up @@ -67,7 +71,8 @@ const createPackageJson = (name) => {
"version": "0.0.0",
"main": "index.umd.js",
"module": "index.es.js",
"style": "style.css"
"style": "style.css",
"types": "../types/${name}/index.d.ts"
}`;

fsExtra.outputFile(path.resolve(outputDir, `${name}/package.json`), fileStr, 'utf-8');
Expand All @@ -81,15 +86,31 @@ exports.build = async () => {
const isDir = fs.lstatSync(componentDir).isDirectory();
return isDir && fs.readdirSync(componentDir).includes('index.ts');
});

const readyToReleaseComponentName = [];
for (const name of components) {
if (!isReadyToRelease(name)) {
continue;
}
readyToReleaseComponentName.push(name);
await buildSingle(name);
createPackageJson(name);
nuxtBuild.createAutoImportedComponent(name);
}

// 生成global.d.ts
try {
execSync(`pnpm run build:components:dts`);
} catch {}
nuxtBuild.createNuxtPlugin();
logger.success('准备生成global.d.ts');
const volarSupportbuildState = volarSupport(replaceIdentifier, readyToReleaseComponentName);
fs.writeFileSync('./build/index.d.ts', `
export * from './types/vue-devui';
import _default from './types/vue-devui';
export default _default;
`);
if (volarSupportbuildState){
logger.success('global.d.ts生成成功');
} else {
logger.error('global.d.ts生成失败, 因为发生错误');
}
};
57 changes: 57 additions & 0 deletions packages/devui-vue/devui-cli/composables/use-extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const ts = require('typescript');
/**
*
* @param {string} code node full text.
* @returns {RegExpMatchArray | null}
*/
function extraComponentName(code){
const regexp = /app\.component\(((?<components>.*)\.name), (?<fileName>.*)\)/;
const groups = regexp.exec(code)?.groups;
if (groups?.components){
return groups.components;
}
}
/**
*
app.directive('file-drop', fileDropDirective);
* @param {string} code
*/
function extraDirective(code){
const regexp = /app\.directive\('(?<directiveName>.*), ?(?<fileName>.*)\);/;
const groups = regexp.exec(code)?.groups;
if (groups?.fileName){
return groups.fileName;
}
}

function extraGlobalProperties(code) {
const globalPropertiesReg = /app\.config\.globalProperties\.(?<serviceName>\$.*) = (?<serviceFileName>.*);/;
const provideReg = /app\.provide\((?<serviceName>.*)\..*, ?new? ?(?<instanceName>.*)\((?<param>.*)\);/gm;
const groups = globalPropertiesReg.exec(code)?.groups || provideReg.exec(code);
if (groups?.serviceName){
return groups.serviceName;
}
}

function extraValue(code){
return extraComponentName(code) ?? extraDirective(code) ?? extraGlobalProperties(code);
}
/**
*
* @param {string} code
*/
function extraType(code){
const isDirective = /app\.directive/.test(code);
const isComponent = /app\.component/.test(code);
const isGlobalProperties = /app\.config\.globalProperties/.test(code);
const isProvide = /app\.provide/.test(code);
if (isDirective) {return 'directive';}
if (isComponent) {return 'component';}
if (isGlobalProperties || isProvide) {return 'service';}
}

exports.extra = extraValue;
exports.extraType = extraType;
exports.extraDirective = extraDirective;
exports.extraComponentName = extraComponentName;
exports.extraGlobalProperties = extraGlobalProperties;
159 changes: 159 additions & 0 deletions packages/devui-vue/devui-cli/composables/use-relation-tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
const ts = require('typescript');
const { extra, extraType } = require('./use-extra');
const {readFileSync} = require('fs');

class componentNode {
/**
*
* @param {String} name componentName
* @param {Boolean} ready
*/
constructor(name){
this.name = name;
/** @type {componentNode} */
this.children = [];
this.type = '';
this.isComponet = false;
}
/**
*
* @param {(node: componentNode) => void} callback
*/
forEachChild(callback){
for (const child of this.children){
callback(child);
}
}
}

class componentRelationTree{
constructor(){
/**
* @type {componentNode}
*/
this.root = new componentNode('root');
}
/**
*
* @param {componentNode} node component relation Node. Used to describe the relationship between components
*/
insert(node){
if (!this.#_hasSameNode(node)){
this.root.children.push(node);
}
}
/**
*
* @param {componentNode} node
* @param {componentNode | componentNode[]} child
*/
insertChild(node, children){
if (this.#_hasSameNode(node)){
for (const child of this.root.children){
if (child.name === node.name){
if (children instanceof Array){
child.childen.push(...children);
} else {
child.children.push(children);
}
}
}
}
}
/**
*
* @param {string} name component name
* @return {componentNode}
*/
find(name){
for (const child of this.root.children){
if (child.name === name){
return child;
}
}
}
/**
*
* @param {componentNode} node
* @return {Boolean}
*/
#_hasSameNode(node){
let idx=0;
let hasSame = false;
while (this.root.children.length !== idx){
/** @type {componentNode} */
const child = this.root.children[idx++];
hasSame = child.name === node.name;
}
return hasSame;
}
}

/**
* @param {string} indexPath
* @return {string}
*/
function readIndexFile(indexPath){
return readFileSync(indexPath).toString();
}
/**
*
* @param {string[]} componentPaths component fold paths
*/
exports.useRelationTree = function (componentPaths){
/**
* @type {ts.SourceFile[]}
*/
const tsPrograms = [];
const tree = new componentRelationTree();
tree.root.type = 'root';
for (const path of componentPaths){
tsPrograms.push(ts.createSourceFile('', readIndexFile(path)));
}
for (const program of tsPrograms){
/**
* @type {ts.ExportDeclaration[]}
*/
const sourceFile = program.getSourceFile();
program.forEachChild((node) => {
if (ts.isExportAssignment(node)){
/**
* @type {ts.ObjectLiteralElement}
*/
const exportObject = node.getChildAt(0, sourceFile);
/** @type {ts.Node[]} */
const properties = exportObject.parent.expression.properties;
/** @type {componentNode} */
let componentTreeNode;
properties.forEach((property) => {
if (ts.isPropertyAssignment(property)){
const Identifier = property.getChildAt(0, sourceFile).getText(sourceFile);
const value = property.getChildAt(2, sourceFile).getText(sourceFile);
if (Identifier === 'title'){
componentTreeNode = new componentNode(value.split(' ')[0].slice(1), true);
}
} else {
/** @type {ts.MethodDeclaration} */
const method = property;
/** @type {ts.Block} */
const block = method.body.getChildAt(1, sourceFile);
const blockChildren = block.getChildren(sourceFile);
for (const child of blockChildren){
const childCode = child.getFullText(sourceFile);
const nodeName = extra(childCode);
const nodeType = extraType(childCode);
const childNode = new componentNode(nodeName);
childNode.type = nodeType;
childNode.isComponet = nodeType === 'component';
if (nodeName){
componentTreeNode.children.push(childNode);
}
}
}
});
tree.insert(componentTreeNode);
}
});
}
return tree.root;
};
36 changes: 36 additions & 0 deletions packages/devui-vue/devui-cli/replaceIdentifer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Modal":{
"Modal":{
"exportKey": "Modal",
"reference": "Modal"
},
"Body":{
"exportKey": "ModalBody",
"reference": "Modal"
},
"Header":{
"exportKey": "ModalHeader",
"reference": "Modal"
},
"Footer":{
"exportKey":"ModalFooter",
"reference": "Modal"
}
},
"ImagePreview":{
"$imagePreviewService":{
"exportKey": "imagePreviewService",
"reference": "ImagePreviewService"
},
"ImagePreviewDirective":{
"exportKey": "DImagePreview",
"reference": "ImagePreviewDirective"
}
},
"Message":{
"$message":{
"exportKey": "message",
"reference": "Message"
}
}
}
Loading