From 9b8bee2bc59019039fbd8eedfcb72490b2915d31 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Wed, 7 Apr 2021 17:03:24 -0400 Subject: [PATCH] add create script --- README.md | 10 +++ docs/resources/changelog.md | 1 + docs/resources/contributing.md | 10 +++ package.json | 3 +- scripts/create-component.cjs | 150 +++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 scripts/create-component.cjs diff --git a/README.md b/README.md index 7102c33a8..ecd87abd8 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,16 @@ To generate a production build, run the following command. npm run build ``` +### Creating New Components + +To scaffold a new component, run the following command, replacing `sl-tag-name` with the desired tag name. + +```bash +npm run create sl-tag-name +``` + +This will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the "Components" section of the sidebar. + ### Contributing Shoelace is an open source project and contributions are encouraged! If you're interesting in contributing, please review the [contribution guidelines](CONTRIBUTING.md) first. diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index f835729d3..59ccb690d 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -10,6 +10,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Added `click()` method to `sl-checkbox`, `sl-radio`, and `sl-switch` - Added the `activation` prop to `sl-tab-group` to allow for automatic and manual tab activation +- Added `npm run create ` script to scaffold new components faster - Fixed a bug in `sl-tooltip` where events weren't properly cleaned up on disconnect - Fixed a bug in `sl-tooltip` where they wouldn't display after toggling `disabled` off and on again [#391](https://github.com/shoelace-style/shoelace/issues/391) - Fixed a bug in `sl-details` where `show()` and `hide()` would toggle the control when disabled diff --git a/docs/resources/contributing.md b/docs/resources/contributing.md index 6dc322723..8063fc6ea 100644 --- a/docs/resources/contributing.md +++ b/docs/resources/contributing.md @@ -90,6 +90,16 @@ npm start After the initial build, a browser will open automatically to a local version of the docs. The documentation is powered by Docsify, which uses raw markdown files to generate pages on the fly. +### Creating New Components + +To scaffold a new component, run the following command, replacing `sl-tag-name` with the desired tag name. + +```bash +npm run create sl-tag-name +``` + +This will generate a source file, a stylesheet, and a docs page for you. When you start the dev server, you'll find the new component in the "Components" section of the sidebar. + ### Dev Sandbox Component development occurs _within_ the local docs site. I've found that offering common variations _in the docs_ is more beneficial for users than segmenting demos and code examples into separate tools such as Storybook. This encourages more thorough documentation, streamlines development for maintainers, and simplifies how the project is built. It also reduces installation and startup times significantly. diff --git a/package.json b/package.json index d429f9209..2ab4f8442 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "start": "node scripts/build.cjs --dev", "build": "node scripts/build.cjs", "prepublishOnly": "npm run build", - "prettier": "prettier --write --loglevel warn ." + "prettier": "prettier --write --loglevel warn .", + "create": "node scripts/create-component.cjs" }, "dependencies": { "@popperjs/core": "^2.7.0", diff --git a/scripts/create-component.cjs b/scripts/create-component.cjs new file mode 100644 index 000000000..2d824ffae --- /dev/null +++ b/scripts/create-component.cjs @@ -0,0 +1,150 @@ +const args = process.argv.slice(2); +const chalk = require('chalk'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const path = require('path'); + +const tagName = (args[0] + '').toLowerCase().trim(); +const tagNameWithoutPrefix = tagName.replace(/^sl-/, ''); +const className = tagName.replace(/(^\w|-\w)/g, string => string.replace(/-/, '').toUpperCase()); +const readableName = tagNameWithoutPrefix + .replace(/-/g, ' ') + .replace(/\w\S*/g, string => string.charAt(0).toUpperCase() + string.substr(1).toLowerCase()); +const version = require('../package.json').version; +const minorVersion = version.split('.').slice(0, 2).join('.'); + +// Check for tag name +if (!tagName) { + console.error('Please provide a tag name for the new component.\n'); + process.exit(); +} + +// Verify tag name prefix +if (!/^sl-/.test(tagName)) { + console.error('Tag names must start with the sl- prefix.\n'); + process.exit(); +} + +// Generate a source file +const componentFile = `src/components/${tagNameWithoutPrefix}/${tagNameWithoutPrefix}.ts`; +if (fs.existsSync(componentFile)) { + console.error(`There is already a component using the <${tagName}> tag!\n`); + process.exit(); +} + +const componentSource = ` +import { LitElement, html, unsafeCSS } from 'lit'; +import { customElement } from 'lit/decorators'; +import styles from 'sass:./${tagNameWithoutPrefix}.scss'; + +/** + * @since ${minorVersion} + * @status experimental + * + * @dependency sl-tag-here + * @dependency sl-tag-here + * + * @slot - The default slot. + * @slot example - A named slot called example. + * + * @part base - The component's base wrapper. + * @part example - Another part called example. + */ +@customElement('${tagName}') +export default class ${className} extends LitElement { + static styles = unsafeCSS(styles); + + render() { + return html\` +
+ +
+ \`; + } +} + +declare global { + interface HTMLElementTagNameMap { + '${tagName}': ${className}; + } +} +`.trimLeft(); + +// Generate a stylesheet +const stylesFile = `src/components/${tagNameWithoutPrefix}/${tagNameWithoutPrefix}.scss`; +const stylesSource = ` +@use '../../styles/component'; + +/** + * @prop --custom-property-here: Description here + */ +:host { + display: block; +} +`.trimLeft(); + +// Generate a docs page +const docsFile = `docs/components/${tagNameWithoutPrefix}.md`; +const docsSource = ` +# ${readableName} + +[component-header:${tagName}] + +Brief description of the component here, followed by an example. + +\`\`\`html preview +<${tagName}> + Hello, world! + +\`\`\` + +## Examples + +### Variation + +A description of the variation, followed by an example. + +\`\`\`html preview +<${tagName}> + Here is a variation + +\`\`\` + +[component-metadata:${tagName}] +`.trimLeft(); + +// Create the files +mkdirp.sync(path.dirname(componentFile)); +mkdirp.sync(path.dirname(stylesFile)); +mkdirp.sync(path.dirname(docsFile)); + +fs.writeFileSync(componentFile, componentSource, 'utf8'); +fs.writeFileSync(stylesFile, stylesSource, 'utf8'); +fs.writeFileSync(docsFile, docsSource, 'utf8'); + +// Add it to shoelace.ts +const allExports = fs.readFileSync('src/shoelace.ts', 'utf8'); +fs.writeFileSync( + 'src/shoelace.ts', + `${allExports.trimRight()}\nexport { default as ${className} } from './components/${tagNameWithoutPrefix}/${tagNameWithoutPrefix}';\n`, + 'utf8' +); + +// Add it to _sidebar.md +const sidebar = fs.readFileSync('docs/_sidebar.md', 'utf8'); +fs.writeFileSync( + 'docs/_sidebar.md', + sidebar.replace('- Components', `- Components\n - [${readableName}](/components/${tagNameWithoutPrefix}.md)`), + 'utf8' +); + +console.log(chalk.green(`The <${tagName}> component has been created:`)); +console.log(` +- created ${componentFile} +- created ${stylesFile} +- created ${docsFile} +- updated src/shoelace.ts +- updated docs/_sidebar.md + +Use ${chalk.cyan('npm start')} to launch the dev server. +`);