Skip to content

Commit

Permalink
feat: add autogenerate component theme types (#775)
Browse files Browse the repository at this point in the history
* refactor volar type generation

* add component theme type generation

* refactor component type definitions

* update components class props descriptions
  • Loading branch information
mlmoravek authored Feb 10, 2024
1 parent fadb780 commit 5abdfe6
Show file tree
Hide file tree
Showing 95 changed files with 2,128 additions and 1,954 deletions.
103 changes: 103 additions & 0 deletions .scripts/gen-comp-types.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// The file is not designed to run directly. `cwd` should be project root.
import fs from 'fs-extra'
import path from 'path'
import process from 'process'

import { createComponentMetaChecker } from '../packages/oruga-next/node_modules/vue-component-meta/out/index.js';

import { componentDirectory, getFolders, getComponents, exist } from "./utils.mjs";

const __dirname = process.cwd()

if(!exist(path.resolve(__dirname, componentDirectory)))
throw new Error("Path not exist: " + componentDirectory);

// create component meta checker
const checker = createComponentMetaChecker(
path.resolve(__dirname, './tsconfig.json'),
{
forceUseTs: true,
printer: { newLine: 1 },
// schema: { ignore: ['MyIgnoredNestedProps'] },
},
);

// get all component folders
const component_folders = getFolders(componentDirectory);

const components = component_folders.map(folder => {
const name = folder.toLowerCase();
const folderPath = path.resolve(__dirname, componentDirectory, folder);

// get all components in component folder
const components = getComponents(folderPath)

// get all configruable props from all components in folder
const props = components.flatMap(comp => {
const file = comp.toLocaleLowerCase()+".vue";
const componentPath = path.resolve(__dirname, componentDirectory, name, file);
const meta = checker.getComponentMeta(componentPath);

return meta.props.filter(prop => {
// filter only class props and configurable props
if(prop.name.includes("Class")) return true;
if(prop.default?.includes("getOption")) {
const path = prop.default.match(/"(.*?)"/)[0];
return path.includes(".");
}
return false;
}).map(prop => {
// change type for class props
if(prop.type === "ComponentClass")
prop.type = "ClassDefinition";

if(prop.name.includes("Classes")) return prop;

// change property name based on config path
if(prop.default && prop.default?.includes("getOption")) {
let name = prop.default.match(/"(.*?)"/)[0];
name = name.substring(1, name.length-1);
const split = name.split(".");
name = split.length == 2 ? split[1] : split[0];
if(prop.name !== name)
prop.name = name;
}
return prop;
})
})
// filter duplicates
.filter((item, idx, self) =>
idx === self.findIndex(p => p.name === item.name)
);

return { name, props };
});


const code = `import type {
ClassDefinition,
ComponentConfigBase,
DynamicComponent,
} from "@/types";
// Auto generated component theme config definition
declare module "../index" {
interface OrugaOptions {
${components.map(({name, props}) =>
`${name.toLowerCase()}?: ComponentConfigBase &
Partial<{${
props.map(prop =>`
/** ${prop.description} */
${prop.name}: ${prop.type};`
).join("")
}
}>;`
).join(`
`)}
}
}
`;

const file = path.resolve(__dirname, componentDirectory, "types.ts");
fs.writeFileSync(path.resolve(__dirname, file), code, 'utf-8')

Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,12 @@ import fs from 'fs-extra'
import path from 'path'
import process from 'process'

const componentDirectory = './src/components';
import { componentDirectory, getComponents, exist } from "./utils.mjs";

// Components to be ignored for creating volar type
const IGNORE = [
"FieldBody",
"SliderThumb",
"TableMobileSort",
"TablePagination",
"PaginationButton",
"DatepickerTable",
"DatepickerTableRow",
"DatepickerMonth",
"PickerWrapper",
"NotificationNotice",
];

const TYPE_ROOT = process.cwd()

function getComponents(dir) {
const files = fs.readdirSync(dir, { recursive: true });
return files
// filter only vue files and remove test files
.filter(f => f.includes(".vue") && !f.includes("tests"))
// remove path
.map(f => path.basename(f))
// remove .vue suffix
.map(f => f.substring(0, f.indexOf(".vue")))
// filter blacklist
.filter((key) => !IGNORE.includes(key))
}

function exist (path) {
return fs.existsSync(path)
}
const __dirname = process.cwd();

function generateComponentsType (module, file) {
if(!exist(path.resolve(TYPE_ROOT, componentDirectory)))
if(!exist(path.resolve(__dirname, componentDirectory)))
throw new Error("Path not exist: " + componentDirectory);

const globalComponents = getComponents(componentDirectory);
Expand All @@ -64,14 +33,15 @@ function generateComponentsType (module, file) {

const code = `// Auto generated component declarations
declare module "vue" {
export interface GlobalComponents {
${lines.join('\n ')}
}
export interface GlobalComponents {
${lines.join(`
`)}
}
}
export {};
`;

fs.writeFileSync(path.resolve(TYPE_ROOT, file), code, 'utf-8')
fs.writeFileSync(path.resolve(__dirname, file), code, 'utf-8')
}

// generate main package volar file
Expand Down
42 changes: 42 additions & 0 deletions .scripts/utils.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// The file is not designed to run directly. `cwd` should be project root.
import fs from 'fs-extra';
import path from 'path';

// Components to be ignored
const IGNORE = [
"FieldBody",
"SliderThumb",
"TableMobileSort",
"TablePagination",
"PaginationButton",
"DatepickerTable",
"DatepickerTableRow",
"DatepickerMonth",
"NotificationNotice",
];

export const componentDirectory = './src/components';

export function exist (path) {
return fs.existsSync(path)
}

export function getFolders(dir) {
const folders = fs.readdirSync(dir)
// remove test and util files
.filter(f => !f.includes("tests") && !f.includes("utils") && !f.includes(".ts"));
return folders;
}

export function getComponents(dir) {
const files = fs.readdirSync(dir, { recursive: true });
return files
// filter only vue files and remove test and util files
.filter(f => f.includes(".vue") && !f.includes("tests") && !f.includes("utils"))
// remove path
.map(f => path.basename(f))
// remove .vue suffix
.map(f => f.substring(0, f.indexOf(".vue")))
// filter blacklist
.filter((key) => !IGNORE.includes(key))
}
28 changes: 28 additions & 0 deletions packages/oruga-next/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions packages/oruga-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,16 @@
"dev": "vite",
"build:vue": "rimraf dist && vite build && vite build --mode minify",
"build:vue:watch": "vite build --mode minify --watch",
"build:lib": "rimraf dist && npm run test:ts && npm run build:vue && npm run gen-volar-dts",
"build:lib": "rimraf dist && npm run test:ts && npm run gen-volar-dts && npm run build:vue && npm run gen-volar-dts",
"build:lib:watch": "npm link && npm run build:vue:watch",
"publish:lib": "cp ../../README.md . && npm run build:lib && npm publish",
"test": "rm -rf .nyc_output coverage && NODE_ENV=test cypress run --component",
"test:cypress": "cypress run --component",
"test:ts": "vue-tsc --noEmit --skipLibCheck",
"test:watch": "rm -rf .nyc_output coverage && NODE_ENV=test cypress open --component",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --quiet --ignore-path .gitignore",
"gen-volar-dts": "node ../../.scripts/gen-component-declaration.mjs --bundle --platform=node",
"gen-volar-dts": "node ../../.scripts/gen-volar-dts.mjs --bundle --platform=node",
"gen-comp-types": "node ../../.scripts/gen-comp-types.mjs --bundle --platform=node",
"update": "ncu -u"
},
"keywords": [
Expand Down Expand Up @@ -104,6 +105,8 @@
"vite-plugin-istanbul": "^5.0.0",
"vite-tsconfig-paths": "^4.3.1",
"vue": "^3.4.18",
"vue-component-meta": "^1.8.27",
"vue-component-type-helpers": "^1.8.27",
"vue-tsc": "^1.8.27"
},
"nyc": {
Expand Down
13 changes: 11 additions & 2 deletions packages/oruga-next/src/components/autocomplete/Autocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ const props = defineProps({
/** Menu tag name */
menuTag: {
type: [String, Object, Function] as PropType<DynamicComponent>,
default: () => getOption("autocomplete.menuTag", "div"),
default: () =>
getOption<DynamicComponent>("autocomplete.menuTag", "div"),
},
/** Menu item tag name */
itemTag: {
type: [String, Object, Function] as PropType<DynamicComponent>,
default: () => getOption("autocomplete.itemTag", "div"),
default: () =>
getOption<DynamicComponent>("autocomplete.itemTag", "div"),
},
/** Options / suggestions */
data: { type: Array, default: () => [] },
Expand Down Expand Up @@ -227,30 +229,37 @@ const props = defineProps({
default: () => getOption("autocomplete.teleport", false),
},
// class props (will not be displayed in the docs)
/** Class of the root element */
rootClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu items */
itemClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu items on hover */
itemHoverClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu items group title */
itemGroupTitleClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu empty placeholder item */
itemEmptyClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu header item */
itemHeaderClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
},
/** Class of the menu footer item */
itemFooterClass: {
type: [String, Array, Function] as PropType<ComponentClass>,
default: undefined,
Expand Down
3 changes: 0 additions & 3 deletions packages/oruga-next/src/components/autocomplete/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import Autocomplete from "./Autocomplete.vue";

import { registerComponent } from "@/utils/plugins";

/** export autocomplete specific types */
export type * from "./types";

/** export autocomplete plugin */
export default {
install(app: App) {
Expand Down
Loading

0 comments on commit 5abdfe6

Please sign in to comment.