Skip to content

Commit

Permalink
feat: enhance create-ui5-element command to create component in TS (#…
Browse files Browse the repository at this point in the history
…6609)

* feat: enhance create-ui5-element command to create component in Typescript
* feat: add second argument as an option to create-ui5-element command for specifying the language
* feat: add third argument as an option for specifying a custom tag
  • Loading branch information
hinzzx authored Mar 8, 2023
1 parent d58da29 commit d870065
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 108 deletions.
213 changes: 105 additions & 108 deletions packages/tools/lib/create-new-component/index.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,7 @@
const fs = require("fs");

const jsFileContentTemplate = componentName => {
return `import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ${componentName}Template from "./generated/templates/${componentName}Template.lit.js";
// Styles
import ${componentName}Css from "./generated/themes/${componentName}.css.js";
/**
* @public
*/
const metadata = {
tag: "${tagName}",
properties: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
slots: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
events: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
};
/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
*
* <h3>Usage</h3>
*
* For the <code>${tagName}</code>
* <h3>ES6 Module Import</h3>
*
* <code>import ${packageName}/dist/${componentName}.js";</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webc.${library}.${componentName}
* @extends sap.ui.webc.base.UI5Element
* @tagname ${tagName}
* @public
*/
class ${componentName} extends UI5Element {
static get metadata() {
return metadata;
}
static get render() {
return litRender;
}
static get styles() {
return ${componentName}Css;
}
static get template() {
return ${componentName}Template;
}
static get dependencies() {
return [];
}
static async onDefine() {
}
}
${componentName}.define();
export default ${componentName};
`;
};
const prompts = require("prompts");
const jsFileContentTemplate = require("./jsFileContentTemplate.js");
const tsFileContentTemplate = require("./tsFileContentTemplate.js");

const getPackageName = () => {
if (!fs.existsSync("./package.json")) {
Expand Down Expand Up @@ -108,47 +35,117 @@ const getLibraryName = packageName => {
return packageName.substr("webcomponents-".length);
};

const camelToKebabCase = string => string.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
// String manipulation
const capitalizeFirstLetter = string => string.charAt(0).toUpperCase() + string.slice(1);

// Validation of user input
const isNameValid = name => typeof name === "string" && name.match(/^[a-zA-Z][a-zA-Z0-9_-]*$/);
const isTagNameValid = tagName => tagName.match(/^([a-z][a-z0-9]*-)([a-z0-9]+(-[a-z0-9]+)*)$/);

const generateFiles = (componentName, tagName, library, packageName, isTypeScript) => {
componentName = capitalizeFirstLetter(componentName);
const filePaths = {
"main": isTypeScript
? `./src/${componentName}.ts`
: `./src/${componentName}.js`,
"css": `./src/themes/${componentName}.css`,
"template": `./src/${componentName}.hbs`,
};

const packageName = getPackageName();
const library = getLibraryName(packageName);
const FileContentTemplate = isTypeScript
? tsFileContentTemplate(componentName, tagName, library, packageName)
: jsFileContentTemplate(componentName, tagName, library, packageName);

const consoleArguments = process.argv.slice(2);
const componentName = consoleArguments[0];
fs.writeFileSync(filePaths.main, FileContentTemplate, { flag: "wx+" });
fs.writeFileSync(filePaths.css, "", { flag: "wx+" });
fs.writeFileSync(filePaths.template, "<div>Hello World</div>", { flag: "wx+" });

if (!componentName){
console.error("Please enter component name.");
return;
console.log(`Successfully generated ${filePaths.main}`);
console.log(`Successfully generated ${filePaths.css}`);
console.log(`Successfully generated ${filePaths.template}`);

// Change the color of the output
console.warn('\x1b[33m%s\x1b[0m', `
Make sure to import the component in your bundle by using:
import ${componentName} from "./dist/${componentName}.js";`);
}

const tagName = `ui5-${camelToKebabCase(componentName)}`;
// Main function
const createWebComponent = async () => {
const packageName = getPackageName();
const library = getLibraryName(packageName);

const filePaths = {
"js": `./src/${componentName}.js`,
"css": `./src/themes/${componentName}.css`,
"hbs": `./src/${componentName}.hbs`,
};
const sJsFileContentTemplate = jsFileContentTemplate(componentName);
const consoleArguments = process.argv.slice(2);
let componentName = consoleArguments[0];
let tagName = consoleArguments[1];
let language = consoleArguments[2];
let isTypeScript;

fs.writeFileSync(filePaths.js, sJsFileContentTemplate, { flag: "wx+" });
fs.writeFileSync(filePaths.css, "", { flag: "wx+" });
fs.writeFileSync(filePaths.hbs, "<div>Hello World</div>", { flag: "wx+" });

if (componentName && !isNameValid(componentName)) {
throw new Error("Invalid component name. Please use only letters, numbers, dashes and underscores. The first character must be a letter.");
}

console.log(`Successfully generated ${componentName}.js`);
console.log(`Successfully generated ${componentName}.css`);
console.log(`Successfully generated ${componentName}.hbs`);
if (tagName && !isTagNameValid(tagName)) {
throw new Error("Invalid tag name. The tag name should only contain lowercase letters, numbers, dashes, and underscores. The first character must be a letter, and it should follow the pattern 'tag-name'.");
}

const bundleLogger = fs.createWriteStream("./bundle.common.js", {
flags: "a" // appending
});
if (language && language !== "typescript" && language !== "ts" && language !== "javascript" && language !== "js") {
throw new Error("Invalid language. Please use 'typescript','javascript' or their respective 'ts','js'.");
}

bundleLogger.write(`
// TODO: Move this line in order to keep the file sorted alphabetically
import ${componentName} from "./dist/${componentName}.js";`);
if (!componentName) {
const response = await prompts({
type: "text",
name: "componentName",
message: "Please enter a component name:",
validate: (value) => isNameValid(value),
});
componentName = response.componentName;

if (!componentName) {
process.exit();
}
}

// Change the color of the output
console.warn('\x1b[33m%s\x1b[0m', `
Component is imported in bundle.common.js.
Do NOT forget to sort the file in alphabeticall order.
`);
if (!tagName) {
const response = await prompts({
type: "text",
name: "tagName",
message: "Please enter a tag name:",
validate: (value) => isTagNameValid(value),
});
tagName = response.tagName;

if (!tagName) {
process.exit();
}
}

if (!language) {
const response = await prompts({
type: "select",
name: "isTypeScript",
message: "Please select a language:",
choices: [
{
title: "TypeScript (recommended)",
value: true,
},
{
title: "JavaScript",
value: false,
},
],
});
isTypeScript = response.isTypeScript;
} else if (language === "typescript" || language === "ts") {
isTypeScript = true;
} else {
isTypeScript = false;
}

generateFiles(componentName, tagName, library, packageName, isTypeScript);
};

createWebComponent();
77 changes: 77 additions & 0 deletions packages/tools/lib/create-new-component/jsFileContentTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const jsFileContentTemplate = (componentName, tagName, library, packageName) => {
return `import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ${componentName}Template from "./generated/templates/${componentName}Template.lit.js";
// Styles
import ${componentName}Css from "./generated/themes/${componentName}.css.js";
/**
* @public
*/
const metadata = {
tag: "${tagName}",
properties: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
slots: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
events: /** @lends sap.ui.webc.${library}.${componentName}.prototype */ {
//
},
};
/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
*
* <h3>Usage</h3>
*
* For the <code>${tagName}</code>
* <h3>ES6 Module Import</h3>
*
* <code>import ${packageName}/dist/${componentName}.js";</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webc.${library}.${componentName}
* @extends sap.ui.webc.base.UI5Element
* @tagname ${tagName}
* @public
*/
class ${componentName} extends UI5Element {
static get metadata() {
return metadata;
}
static get render() {
return litRender;
}
static get styles() {
return ${componentName}Css;
}
static get template() {
return ${componentName}Template;
}
static get dependencies() {
return [];
}
static async onDefine() {
}
}
${componentName}.define();
export default ${componentName};
`;
};

module.exports = jsFileContentTemplate;
84 changes: 84 additions & 0 deletions packages/tools/lib/create-new-component/tsFileContentTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const tsFileContentTemplate = (componentName, tagName, library, packageName) => {
return `import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ${componentName}Template from "./generated/templates/${componentName}Template.lit.js";
// Styles
import ${componentName}Css from "./generated/themes/${componentName}.css.js";
/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
*
* <h3>Usage</h3>
*
* For the <code>${tagName}</code>
* <h3>ES6 Module Import</h3>
*
* <code>import ${packageName}/dist/${componentName}.js";</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webc.${library}.${componentName}
* @extends sap.ui.webc.base.UI5Element
* @tagname ${tagName}
* @public
*/
@customElement({
tag: "${tagName}",
renderer: litRender,
styles: ${componentName}Css,
template: ${componentName}Template,
dependencies: [],
})
/**
* Example custom event.
* Please keep in mind that all public events should be documented in the API Reference as shown below.
*
* @event sap.ui.webc.${library}.${componentName}#interact
* @public
*/
@event("interact", { detail: { /* event payload ( optional ) */ } })
class ${componentName} extends UI5Element {
/**
* Defines the value of the component.
*
* @type {string}
* @name sap.ui.webc.${library}.${componentName}.prototype.value
* @defaultvalue ""
* @public
*/
@property()
value!: string;
/**
* Defines the text of the component.
*
* @type {Node[]}
* @name sap.ui.webc.${library}.${componentName}.prototype.default
* @slot
* @public
*/
@slot({ type: Node, "default": true })
text!: Array<Node>;
static async onDefine() {
}
}
${componentName}.define();
export default ${componentName};
`;
};

module.exports = tsFileContentTemplate;

0 comments on commit d870065

Please sign in to comment.