Skip to content

Commit

Permalink
feat(vue): TypeScript definition for all Vue components
Browse files Browse the repository at this point in the history
  • Loading branch information
nolimits4web committed Jul 14, 2021
1 parent 62ea344 commit b6d599a
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 67 deletions.
67 changes: 1 addition & 66 deletions scripts/build-react-typings.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,7 @@

const getOutput = require('./get-output');
const fs = require('./utils/fs-extra');

const COLOR_PROPS = `
color?: string;
colorTheme?: string;
textColor?: string;
bgColor?: string;
borderColor?: string;
rippleColor?: string;
themeDark?: boolean;
`;

const ICON_PROPS = `
icon?: string;
iconMaterial?: string;
iconF7?: string;
iconIos?: string;
iconMd?: string;
iconAurora?: string;
iconColor?: string;
iconSize?: string | number;
`;

const ROUTER_PROPS = `
back?: boolean;
external?: boolean;
force?: boolean;
animate?: boolean;
ignoreCache?: boolean;
reloadCurrent?: boolean;
reloadAll?: boolean;
reloadPrevious?: boolean;
reloadDetail?: boolean;
routeTabId?: string;
view?: string;
routeProps?: any;
preventRouter?: boolean;
transition?: string;
openIn?: string;
`;

const ACTIONS_PROPS = `
searchbarEnable?: boolean | string;
searchbarDisable?: boolean | string;
searchbarClear?: boolean | string;
searchbarToggle?: boolean | string;
panelOpen?: boolean | string;
panelClose?: boolean | string;
panelToggle?: boolean | string;
popupOpen?: boolean | string;
popupClose?: boolean | string;
actionsOpen?: boolean | string;
actionsClose?: boolean | string;
popoverOpen?: boolean | string;
popoverClose?: boolean | string;
loginScreenOpen?: boolean | string;
loginScreenClose?: boolean | string;
sheetOpen?: boolean | string;
sheetClose?: boolean | string;
sortableEnable?: boolean | string;
sortableDisable?: boolean | string;
sortableToggle?: boolean | string;
cardOpen?: boolean | string;
cardPreventOpen?: boolean | string;
cardClose?: boolean | string;
menuClose?: boolean | string;
`;
const { COLOR_PROPS, ICON_PROPS, ROUTER_PROPS, ACTIONS_PROPS } = require('./ts-extend-props');

function generateComponentProps(propsContent) {
// eslint-disable-next-line
Expand Down
169 changes: 169 additions & 0 deletions scripts/build-vue-typings.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,168 @@

const getOutput = require('./get-output');
const fs = require('./utils/fs-extra');
const { COLOR_PROPS, ICON_PROPS, ROUTER_PROPS, ACTIONS_PROPS } = require('./ts-extend-props');

const extendMap = {
colorProps: COLOR_PROPS,
iconProps: ICON_PROPS,
routerProps: ROUTER_PROPS,
actionsProps: ACTIONS_PROPS,
};

function generateComponentEvents(fileContent) {
if (!fileContent.includes('emits: [')) return '';
let events;
fileContent.replace(/emits: \[([^\]]*)\]/, (str, eventsString) => {
events = eventsString
.split(',')
.map((ev) => ev.replace(/'/g, '').trim())
.filter((ev) => !!ev);
});
if (events && events.length) {
return [
`(${events.map((ev) => `"${ev}"`).join(' | ')})[]`,
`${events.map((ev) => `"${ev}"`).join(' | ')}`,
].join(',\n ');
}
return '';
}

function generateComponentProps(fileContent) {
// eslint-disable-next-line
let propsContent = fileContent.match(/props\:\ {([^ъ]*)},\n (emits|setup)/g);
if (propsContent && propsContent[0]) {
propsContent = propsContent[0]
.replace('props: {', '')
.replace('},\n emits', '')
.replace('},\n setup', '');
}
if (!propsContent) {
return '';
}
const props = [];
const extendWith = [];
(propsContent.match(/\.\.\.([a-zA-Z]*)/g) || []).forEach((prop) => {
extendWith.push(prop.replace('...', ''));
});

const getType = (str) => {
if (str.includes('['))
str = str
// eslint-disable-next-line
.replace(/[\[\]]*/g, '')
.split(', ')
.map((t) => t.replace(',', ''));
else if (str.includes(',')) str = str.replace(',', '');
return str;
};

// eslint-disable-next-line
propsContent.replace(/\n ([a-zA-Z0-9]*): ([\[\], a-zA-Z0-9]*)/g, (str, name, type) => {
if (!type) return;
props.push({ name, type: getType(type) });
});

// eslint-disable-next-line
propsContent.replace(/\n ([a-zA-Z0-9]*): {([^ъ^\}]*)}/g, (str1, name, typeObj) => {
const prop = { name };
// eslint-disable-next-line
typeObj.replace(/type: ([\[\], a-zA-Z0-9]*)/, (str2, typeStr) => {
prop.type = getType(typeStr);
});
if (typeObj.includes('default: ')) {
prop.default = typeObj.split('default: ')[1].replace(',\n', '').trim();
}
props.push(prop);
});

const toVueType = (type) => {
if (type === 'string') return 'String';
if (type === 'boolean') return 'Boolean';
if (type === 'number') return 'Number';
if (type === 'any') return 'Object';
return '';
};

extendWith.forEach((extendGroup) => {
extendMap[extendGroup]
.trim()
.split('\n')
.forEach((propsRow) => {
// eslint-disable-next-line
let [name, type] = propsRow.replace(/;/, '').split('?: ');
if (type.includes(' | ')) {
type = type.split(' | ').map(toVueType);
} else {
type = toVueType(type);
}
props.push({ name, type });
});
});

const getVueType = ({ type }) => {
const plainType = (t) => {
if (t === 'String') return 'StringConstructor';
if (t === 'Boolean') return 'BooleanConstructor';
if (t === 'Number') return 'NumberConstructor';
if (t === 'Function') return 'FunctionConstructor';
if (t === 'Object') return 'ObjectConstructor';
if (t === 'Array') return 'ArrayConstructor';
return 'any';
};
if (typeof type === 'string') {
return plainType(type);
}
return `PropType<${type.map(plainType).join(' | ')}>`;
};

const getVueDefault = (prop) => {
if ('default' in prop) {
const value = prop.default;
if (value.includes("'")) return 'string';
if (value === 'true' || value === 'false') return 'boolean';
if (!Number.isNaN(parseFloat(value))) return 'number';
return `${prop.default}`;
}
return '';
};

return props
.map((prop) => {
const defaultValue = getVueDefault(prop);
return `
${prop.name}: {
type: ${getVueType(prop)};${defaultValue ? `\n default: ${defaultValue};` : ''}
}`;
})
.join(',\n');
}

function generateComponentTypings(componentName, fileContent) {
if (componentName.includes('Swiper') || componentName.includes('Skeleton')) return fileContent;
return `
import { ComponentOptionsMixin, DefineComponent, PropType } from 'vue';
// IMPORTS
declare const ${componentName}: DefineComponent<
{
// PROPS
},
() => JSX.Element,
unknown,
{},
{},
ComponentOptionsMixin,
ComponentOptionsMixin,
// EVENTS
>;
export default ${componentName};
`
.replace('// IMPORTS', '')
.replace('// PROPS', generateComponentProps(fileContent))
.replace('// EVENTS', generateComponentEvents(fileContent));
}

function buildTypings(cb) {
const output = `${getOutput()}/vue`;
Expand All @@ -22,6 +184,13 @@ function buildTypings(cb) {
const fileBase = fileName.replace('.vue', '');
componentImports.push(`import ${componentName} from './components/${fileBase}';`);
componentExports.push(componentName);

const typingsContent = generateComponentTypings(
componentName,
fs.readFileSync(`src/vue/components/${fileName}`, 'utf-8'),
);

fs.writeFileSync(`${output}/components/${fileBase}/${fileBase}.d.ts`, typingsContent);
});

const mainTypings = fs
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-vue.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function buildVue(cb) {
main: `../../cjs/components/${fileBase}.js`,
module: `../../esm/components/${fileBase}.js`,
'jsnext:main': `../../esm/components/${fileBase}.js`,
// typings: `${fileBase}.d.ts`,
typings: `${fileBase}.d.ts`,
};
fs.writeFileSync(
`${buildPath}/vue/components/${fileBase}/package.json`,
Expand Down
65 changes: 65 additions & 0 deletions scripts/ts-extend-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module.exports.COLOR_PROPS = `
color?: string;
colorTheme?: string;
textColor?: string;
bgColor?: string;
borderColor?: string;
rippleColor?: string;
themeDark?: boolean;
`;

module.exports.ICON_PROPS = `
icon?: string;
iconMaterial?: string;
iconF7?: string;
iconIos?: string;
iconMd?: string;
iconAurora?: string;
iconColor?: string;
iconSize?: string | number;
`;

module.exports.ROUTER_PROPS = `
back?: boolean;
external?: boolean;
force?: boolean;
animate?: boolean;
ignoreCache?: boolean;
reloadCurrent?: boolean;
reloadAll?: boolean;
reloadPrevious?: boolean;
reloadDetail?: boolean;
routeTabId?: string;
view?: string;
routeProps?: any;
preventRouter?: boolean;
transition?: string;
openIn?: string;
`;

module.exports.ACTIONS_PROPS = `
searchbarEnable?: boolean | string;
searchbarDisable?: boolean | string;
searchbarClear?: boolean | string;
searchbarToggle?: boolean | string;
panelOpen?: boolean | string;
panelClose?: boolean | string;
panelToggle?: boolean | string;
popupOpen?: boolean | string;
popupClose?: boolean | string;
actionsOpen?: boolean | string;
actionsClose?: boolean | string;
popoverOpen?: boolean | string;
popoverClose?: boolean | string;
loginScreenOpen?: boolean | string;
loginScreenClose?: boolean | string;
sheetOpen?: boolean | string;
sheetClose?: boolean | string;
sortableEnable?: boolean | string;
sortableDisable?: boolean | string;
sortableToggle?: boolean | string;
cardOpen?: boolean | string;
cardPreventOpen?: boolean | string;
cardClose?: boolean | string;
menuClose?: boolean | string;
`;

0 comments on commit b6d599a

Please sign in to comment.