Skip to content
This repository was archived by the owner on Sep 26, 2022. It is now read-only.

feat(api-generator): generate types from components automatically #76

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
"svelte": "^3.30.0"
"svelte": "^3.31.0"
},
"husky": {
"hooks": {
Expand Down
2 changes: 1 addition & 1 deletion packages/api-generator/dist/index.js

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions packages/api-generator/helpers/generate.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
/* eslint-disable no-console */
const { writeFile } = require('fs');
const { writeFile, existsSync, mkdirSync } = require('fs');
const { basename } = require('path');
const globby = require('globby');
const sveltedoc = require('sveltedoc-parser');
const fmt = require('json-format');
const rollup = require('rollup');
const json = require('@rollup/plugin-json');
const { terser } = require('rollup-plugin-terser');
const { generateTypings, TYPINGS_PATH } = require('./typings');

if (!existsSync(TYPINGS_PATH)) {
mkdirSync(TYPINGS_PATH);
}

const defaults = { defaultVersion: 3 };

Expand All @@ -22,6 +27,8 @@ async function generateJSON(filename) {
if (err) throw err;
console.log(`${name}.json has been saved`);
});

return Promise.resolve(doc);
}

async function indexjs(paths) {
Expand All @@ -48,13 +55,29 @@ async function build() {
}

(async () => {
const generateTypes = process.argv.slice(2).includes('--types');
let paths = await globby('../svelte-materialify/src/**/*.svelte');
paths.forEach(generateJSON);

const promisesOfJson = paths.map(async (path) => {
const doc = await generateJSON(path);
return Promise.resolve(doc);
});

paths = paths.map((name) => basename(name, '.svelte'));
await writeFile('./src/all.json', fmt({ names: paths }, format), (err) => {
if (err) throw err;
console.log('all.json has been saved');
});

if (generateTypes) {
const types = await generateTypings(Promise.all(promisesOfJson));
const typingsBundle = types.map((t) => t.content).join('\n');
writeFile(`${TYPINGS_PATH}/index.d.ts`, typingsBundle, (err) => {
if (err) throw err;
console.log('index.d.ts has been saved');
});
}

indexjs(paths);
build();
})();
73 changes: 73 additions & 0 deletions packages/api-generator/helpers/handlebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
function ifNotHelper(value, options) {
return !value ? options.fn(this) : options.inverse(this);
}

function ifEqualHelper(arg1, arg2, options) {
return (arg1 === arg2) ? options.fn(this) : options.inverse(this);
}

function ifNotEqualHelper(arg1, arg2, options) {
return (arg1 !== arg2) ? options.fn(this) : options.inverse(this);
}

function ifExistsHelper(value, options) {
return value !== undefined ? options.fn(this) : options.inverse(this);
}

function ifHasDocumentationHelper(prop, options) {
const nonTypeKeywords = prop.keywords && prop.keywords.filter((kw) => kw.name !== 'type');
const hasNonTypeKeywords = !!(nonTypeKeywords && nonTypeKeywords.length);
const hasDefaultValue = prop.defaultValue !== undefined;
const hasDescription = !!prop.description;

return (hasNonTypeKeywords || hasDefaultValue || hasDescription) ?
options.fn(this) :
options.inverse(this);
}

function defaultValueHelper(prop) {
const value = prop.defaultValue;
let { type } = prop.type;

// Get internal value from a Union Type
if (Array.isArray(type)) {
const found = type.find((t) => t.value === value);
if (found) type = found.type;
}
const returned = type === 'string' ? `'${value}'` : `${value}`;

return returned;
}

const KnownTypes = {
RippleOptions: { default: false, path: './Ripple' },
};

function importKnownTypesHelper(doc) {
// Check doc from known types and add import statement to d.ts
const imports = doc.data
.map((p) => ({ type: p.type.text, ...KnownTypes[p.type.text] }))
.filter((t) => !!t.path);
return imports.map((i) => 'import ' +
`${i.default ? '' : '{ '}` +
`${i.type}` +
`${i.default ? '' : ' }'} ` +
`from '${i.path}';`);
}

function propTypeHelper(prop) {
return `${prop.type.text}`;
}

function registerHelpers(handlebars) {
handlebars.registerHelper('ifNot', ifNotHelper);
handlebars.registerHelper('ifEqual', ifEqualHelper);
handlebars.registerHelper('ifNotEqual', ifNotEqualHelper);
handlebars.registerHelper('ifExists', ifExistsHelper);
handlebars.registerHelper('ifHasDocumentation', ifHasDocumentationHelper);
handlebars.registerHelper('defaultValue', defaultValueHelper);
handlebars.registerHelper('propType', propTypeHelper);
handlebars.registerHelper('importKnownTypes', importKnownTypesHelper);
}

module.exports.registerHelpers = registerHelpers;
67 changes: 67 additions & 0 deletions packages/api-generator/helpers/typings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable no-console */

const { writeFile, readFileSync } = require('fs');
const path = require('path');
const Handlebars = require('handlebars');
const { registerHelpers } = require('./handlebars');

const TYPINGS_PATH = path.resolve('../svelte-materialify/src');

const headerPath = './templates/header.handlebars';
const templatePath = './templates/component.handlebars';

registerHelpers(Handlebars);

const headerTemplate = readFileSync(headerPath, 'utf8');
const compiledHeader = Handlebars.compile(headerTemplate);
const headerContent = {
imports: [
"import { SvelteComponentTyped } from 'svelte'",
"import { TransitionConfig, blur, crossfade, draw, fade, fly, scale, slide } from 'svelte/transition'",
"import { RippleOptions } from './@types/Ripple'",
],
};

const template = readFileSync(templatePath, 'utf8');
const compiledTemplate = Handlebars.compile(template);

/**
* @param {Promise<Object[]>} promises promises for sveltedoc-parser generated objects
* @param {boolean} save save the declaration in a separate file
* @returns {Promise<{ name: string; content: string }[]>}
*/
async function generateTypings(promises, save = false) {
const docs = await promises;

const templates = docs.map((doc) => compiledTemplate(doc));

const declarations = [{
name: '__header__',
content: compiledHeader(headerContent),
}];

declarations.push(...templates.map(
(t, i) => ({ name: docs[i].name, content: t }),
));

if (save) {
const promisesOfSavedFiles = declarations.map(
({ name, content }) => new Promise((resolve, reject) => {
writeFile(`${TYPINGS_PATH}/${name}.d.ts`, content, (err) => {
if (err) reject(err);
console.log(`${name}.d.ts has been saved`);
resolve({ name, content });
});
}),
);

return Promise.all(promisesOfSavedFiles);
}

return Promise.resolve(declarations);
}

module.exports = {
generateTypings,
TYPINGS_PATH,
};
4 changes: 3 additions & 1 deletion packages/api-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
"main": "dist/index.js",
"module": "dist/index.js",
"scripts": {
"api": "node helpers/generate.js"
"api:json": "node helpers/generate.js",
"api": "node helpers/generate.js --types"
},
"devDependencies": {
"@rollup/plugin-json": "^4.1.0",
"globby": "^11.0.1",
"handlebars": "^4.7.6",
"json-format": "1.0.1",
"rollup": "^2.33.3",
"rollup-plugin-terser": "^7.0.2",
Expand Down
85 changes: 68 additions & 17 deletions packages/api-generator/src/Button.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,53 @@
"defaultValue": false
},
{
"keywords": [
{
"name": "type",
"description": "{'x-small' | 'small' | 'default' | 'large' | 'x-large'}"
}
],
"visibility": "public",
"description": null,
"keywords": [],
"description": "The size of the button.",
"name": "size",
"kind": "let",
"static": false,
"readonly": false,
"type": {
"kind": "type",
"text": "string",
"type": "string"
"kind": "union",
"text": "'x-small' | 'small' | 'default' | 'large' | 'x-large'",
"type": [
{
"kind": "const",
"text": "'x-small'",
"type": "string",
"value": "x-small"
},
{
"kind": "const",
"text": "'small'",
"type": "string",
"value": "small"
},
{
"kind": "const",
"text": "'default'",
"type": "string",
"value": "default"
},
{
"kind": "const",
"text": "'large'",
"type": "string",
"value": "large"
},
{
"kind": "const",
"text": "'x-large'",
"type": "string",
"value": "x-large"
}
]
},
"defaultValue": "default"
},
Expand Down Expand Up @@ -153,17 +189,22 @@
"defaultValue": false
},
{
"keywords": [
{
"name": "type",
"description": "{boolean}"
}
],
"visibility": "public",
"description": null,
"keywords": [],
"description": "",
"name": "disabled",
"kind": "let",
"static": false,
"readonly": false,
"type": {
"kind": "type",
"text": "object",
"type": "object"
"text": "boolean",
"type": "boolean"
},
"defaultValue": null
},
Expand Down Expand Up @@ -213,31 +254,41 @@
"defaultValue": "button"
},
{
"keywords": [
{
"name": "type",
"description": "{RippleOptions}"
}
],
"visibility": "public",
"description": null,
"keywords": [],
"description": "",
"name": "ripple",
"kind": "let",
"static": false,
"readonly": false,
"type": {
"kind": "type",
"text": "any",
"type": "any"
"text": "RippleOptions",
"type": "RippleOptions"
}
},
{
"keywords": [
{
"name": "type",
"description": "{string}"
}
],
"visibility": "public",
"description": null,
"keywords": [],
"description": "",
"name": "style",
"kind": "let",
"static": false,
"readonly": false,
"type": {
"kind": "type",
"text": "object",
"type": "object"
"text": "string",
"type": "string"
},
"defaultValue": null
}
Expand Down
Loading