Skip to content

Single source web template generator with support for React, SilverStripe, Vue, and more...

License

Notifications You must be signed in to change notification settings

robbyahn/metatemplate

 
 

Repository files navigation

MetaTemplate 🦚

MetaTemplate is a web template/component generator that can take a single template definition and output multiple templates/components in:

  • React JavaScript or TypeScript, with or without Styled-Components
  • Vue (beta)
  • SilverStripe Components (alpha)
  • Mustache/Handlebars (beta)
  • Twig (Drupal / PHP) (beta)
  • CSS/SCSS with SCSS Variables and CSS Variables
  • HTML

This is particularly useful for Design Systems / Pattern Libraries where a single template definition could be converted into multiple template/components.

The input format to generate these is standard CSS, and almost standard HTML (called MetaHTML -- see docs below).

🎁 Features

  • Single-source template generator.
  • MetaTemplate bundles only the CSS relevant to your HTML, so give it your whole CSS file and then MetaTemplate will try to 'tree shake' your CSS, SCSS, and Styled Components declarations.
  • It can generate code examples to show example usage of these component formats.
  • SCSS/CSS Variable replacement... define substring matches in CSS values and replace them with Scss Variables and CSS Variables. Match a colour of "#336699" and replace it with variable named theme-color-background that will be replaced in situ with references to CSS Variables of --theme-color-background, and those will also be Scss/CSS variables that you can define at ./scss/_settings.scss.

🌴 Examples

🌰 Very basic example: input tag

Input: MetaHTML and standard CSS.

Output: React JS, React TS, React JS with Styled Components, React TS with Styled Components, Mustache/Handlebars, SilverStripe Components, Vue, Twig, Sass (SCSS), and finally HTML and CSS.


These next two examples come from FlexBoxGrid.com and we've chosen two components with different complexities.

🌱 Slightly more complicated example: FlexBox Container

Input: MetaHTML and standard CSS.

Output: React JS, React TS, React JS with Styled Components, React TS with Styled Components, Mustache/Handlebars, SilverStripe Components, Sass (SCSS), Vue, Twig, and finally HTML and CSS.

🌳 Complex example: FlexBox Column

Input: MetaHTML and standard CSS.

Output: React JS, React TS, React JS with Styled Components, React TS with Styled Components, Mustache/Handlebars, SilverStripe, Sass (SCSS), Vue, Twig, and finally HTML and CSS.

Install

npm i @springload/metatemplate or yarn add @springload/metatemplate.

🔮 Future

  • More template formats... contribute your favourite!
  • Better CSS support.
  • Loops, although because we support children (childNode) values you could just nest other components instead. Maybe we don't need this.
  • More tests..we have basic Jest tests, and basic React tests.

⚠️ Limitations

  • The CSS 'tree shaking' can't handle complicated CSS such as :not(.class) and probably other features too, so check the output formats yourself.
  • This library uses JSDOM and optionally Puppeteer to parse HTML/CSS which mimics a browser environment inside Node.js. The JSDOM developers themselves note that it's possible to escape their sandbox when given malicious input, so don't use untrusted input, ya dingus.

📡 API

TypeScript types are provided.

makeTemplate

async function makeTemplates(template, formatIds, options) => {}

The purpose of this function is to return templates in a variety of formats.

It's an async function that takes a Template object, an optional array of template format ids, and optional –well– options. If the 2nd argument isn't provided a default list of template format ids is used instead. options is an Object shaped like { async: true, dom: "jsdom", log: false }. The async configures the internal processing of templates as either syncronous or asyncronous (the default). The dom can be either jsdom (the default) or puppeteer. Finally, log just makes MetaTemplate console.log a few more details about the conversion, like a verbose mode.

Returns a promise that resolves to a { metaTemplates, disposeAll }.

  • metaTemplates is an array of { templateFormat, files } where
    • files is an Object that represents a file archive, with Object keys as paths and values as strings of the templates. ie, { 'scss/button.scss': 'scss file data', 'mustache/button.mustache': 'mustache template data' }.
    • templateFormat is a string of the template format (react-js or scss or react-ts-styled-components etc).
  • disposeAll is a function to dispose of shared resources and after you finish using MetaTemplate. disposeAll should be called to avoid memory/CPU waste. This should be called regardless of your configuration, however this is most important for people using Puppeteer rather than JSDOM (JSDOM is the default). Why disposeAll? Well if MetaTemplate automatically cleaned up and called disposeAll internally before returning your templates then it would be slower at batch processing, which is what MetaTemplate is typically used for. Perhaps this behaviour should be in options.

The 1st argument Template Object looks like,

{ id, html, css, cssVariables }
  • id is a required string that is used as the TemplateId. This is your arbitrary but unique name for this template so use something meaningful. ie, FlexColumn.
  • html is a required string of MetaHTML.
  • css is a required string of standard CSS.
  • cssVariables is an optional Array of Objects shaped like { id, defaultValue, nameMatch, valueMatch, valueSubstringMatch },
    • id is a required string for the variable name that will be made in Scss Variables and CSS Variables.
    • defaultValue: is a required string of your preferred default value for this variable.
    • nameMatch, valueMatch, and valueSubstringMatch are all optional strings, and you would choose one of them to match the CSS that you want to insert a variable at. If you want to replace a substring ie, "#000000" with "theme-color-dark" then you might write [{ id: 'theme-color-black', defaultValue: '#000000', valueSubstringMatch: '#000000' }] to match that substring and replace it with variables. Currently there's no way of not outputting CSS Variables.

MetaHTML ?

The reason why we need to use non-standard HTML is to know which parts should be configurable, as variables.

MetaHTML is standard HTML with two types of template variables:

  • Those variables in attribute values:

    • For making a required variable string: {{ variableName }} eg <span class="{{ class }}">
      • Use a ? after the variable name to make it optional
      • Multiple variables can exist in an attribute value, write them like <span class="{{ class }}{{ otherClass }}">
    • For making a required variable with enumerations {{ variableName: option1 | option2 }} eg <span class="{{ color: class-red | class-blue }}">
    • For making a variable with enumerations that have friendly names {{ variableName: option1 as Option1 | option2 as Option2 }} eg &lt;span class="{{ color: class-red as Red | class-blue as Blue }}"&gt;
  • Those variables that are childNodes between elements:

    • Use <mt-variable key="variableName">default value</mt-variable> eg if you want a component variable named "children" in an &lt;h1&gt; you'd write <h1><mt-variable key="children">placeholder</mt-variable></h1>

There is also template if support as <mt-if key="isShown">thing to show</mt-if>.

makeIndexImports

async function makeIndexImports( files, cssVariables ) => {}

The purpose of this function is to provide "index" definitions for each format. The exact details vary by format, but the Sass (Scss) makes an index.scss file with lots of @import "thing.scss", and a _settings.scss for the Scss variables (hence the cssVariables argument). The JavaScript/TypeScript templates have lazy-loaded imports in index.js or index.ts.

The files Object is a required variable that represents a file archive, with Object keys as paths and values as strings of the file data. Typically you'd want to return the files object returned by the default export.

cssVariables is an optional Object with the same shape as the cssVariables argument given to the default export.

Returns a files Object that represents a file archive, but now it has index files for each format (ie, "scss/index.scss") to assist with importing files for that format.

makeUsage

async function makeUsage( code, templates, formatIds ) => {}

The purpose of this function is to convert a single code example (eg, in documentation) into examples from a variety of template formats.

code is a code example,

  • TIP: Try jsxToUsageCode for a slightly easier way of generating code.

It's datastructure is an Array of TemplateConfig Objects shaped like,

{ templateId, variables }

Where templateId is a string of the TemplateId (same as the default export... your arbitrary id for the template), and variables is an Object keyed by variableNames whose values can be strings or other TemplateConfig Objects. ie, [{ templateId: 'H1', variables: { children: 'Hello' } }] or [{ templateId: 'H1', variables: { children: [{ templateId: 'A', variables: { href: 'https://html5zombo.com/', children: 'Click me' } }] } }].

templates is a required Object keyed by strings of TemplateIds whose values are Template Objects (like the default export). This must include every templateId referenced in code.

formatIds is an optional array of strings of the formats to use, with a complete list of formatIds as the default.

jsxToUsageCode

async function jsxToUsageCode( jsx ) => {}

A convenience function that parses a string of React JSX and returns a code variable for the makeUsage function.

  • jsx is a required string of JSX ie, '<H1><A href="https://html5zombo.com/">Click me</A></H1>' will return [{ templateId: 'H1', variables: { children: [{ templateId: 'A', variables: { href: 'https://html5zombo.com/', children: 'Click me' } }] } }]

About

Single source web template generator with support for React, SilverStripe, Vue, and more...

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 98.2%
  • JavaScript 1.8%