npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

logicful-templates

v3.0.3

Published

A library allowing you to build HTML templates using React & JSX

Downloads

28

Readme

Logicful templates

codecov build

A wrapper over React's renderToStaticMarkup for building logicful HTML templates, using JSX, transform hooks, and a hint of magic. 🧙

const Template = () => {
  const employees = [
    { name: 'Petra', age: 33, title: 'Chief Template Creator' },
    { name: 'John', age: 31, title: 'Template Hacker' },
    { name: 'Jacky', age: 26, title: 'Senior Template Engineer' },
    { name: 'Boris', age: 28, title: 'Template Acquisition Expert' },
  ];

  return (
    <html lang='en'>
      <head>
        <title>An example JSX template</title>
        <meta charSet='UTF-8' />
        <script type='text/javascript' dangerouslySetInnerHTML={{ __html: 'alert("An in-your-face message!")' }} />
      </head>
      <body>
        <div className='employees'>
          {employees.map((employee) => (
            <div key={employee.name} className='employee'>
              <div className='name'>
                {employee.name}, {employee.age}
              </div>
              <div className='title'>{employee.title}</div>
            </div>
          ))}
        </div>
      </body>
    </html>
  );
};

would compile into

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>An example JSX template</title>
    <meta charset="UTF-8" />
    <script type="text/javascript">
      alert("An in-your-face message!");
    </script>
  </head>
  <body>
    <div class="employees">
      <div class="employee">
        <div class="name">Petra, 33</div>
        <div class="title">Chief Template Creator</div>
      </div>
      <div class="employee">
        <div class="name">John, 31</div>
        <div class="title">Template Hacker</div>
      </div>
      <div class="employee">
        <div class="name">Jacky, 26</div>
        <div class="title">Senior Template Engineer</div>
      </div>
      <div class="employee">
        <div class="name">Boris, 28</div>
        <div class="title">Template Acquisition Expert</div>
      </div>
    </div>
  </body>
</html>

Usage

Setup

Install the library using:

npm i logicful-templates

TypeScript configuration

If you're using TypeScript, ensure you're using a version larger than 4.1 as we'll be using the jsxImportSource configuration to load the react JSX runtime.

Add the configuration below to your tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "module": "commonjs",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true
  },
}

JavaScript + Babel configuration

If you're using plain JavaScript we'll need Babel to transpile the JSX syntax into regular JS.

Add the configuration below to your Babel configuration file:

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "runtime": "automatic",
        "importSource": "react"
      }
    ]
  ]
}

Function overview

compileTemplate

Compiles a component and spits out an HTML string.

A function that compiles a React component into a plain HTML string. This is simply a wrapper over react-dom/server's renderToStaticMarkup function, allowing us to specify before and after compilation hooks, which also power the magic components.

Arguments

  • element: A ReactElement or a function returning one e.g. <Template /> or () => <Template />
  • compileOptions: An object taking various compilation options:
    • addDocType: Whether to add the <!doctype html> string at the start of the output (default: false)
    • pretty: Whether to format the output with Prettier (default: false)
    • prettyOptions: Custom options for Prettier (see this page) (default: { parser: 'html' })

Note: If you specify both addDocType: true and pretty: true the doctype will be formatted as <!DOCTYPE html>

import type { FunctionComponent } from 'react';
import LogicfulTemplates from 'logicful-templates';

const Template: FunctionComponent<{}> = () => (
  <div className="greeting">Hello World</div>
);

const result = LogicfulTemplates.compileTemplate(<Template />);
// result: <div class="greeting">Hello World</div>

// Write output to a file
fs.writeFile("template.html", result, () => {});

registerHook

Register a before or after hook to execute during compilation.

For example:

  • Setting addDocType to true registers an after hook, ensuring the output starts with <!doctype html>
  • Setting pretty to true registers an after hook, tidying up HTML before returning it.

You can find the source of above hooks below:

Internally hooks are being used to make <Magic> and <Comment> components possible, they simply render a placeholder which is then replaced when the after hook is executed. You can check out their implementation below:

If you have a good idea for other hooks, PRs are more than welcome. 🚀

Registering a before hook

before hooks execute before calling renderToStaticMarkup, they aren't called with any parameters, but they allow you to execute any type of logic before compilation starts. For example:

LogicfulTemplates.registerHook('before', () => {
  console.log('Starting compilation...');
});

Registering an after hook

after hooks execute after calling renderToStaticMarkup, they are called with the output of either that call, or the output of the previously executed after hook. For example:

LogicfulTemplates.registerHook('after', (html) => {
  return html.toUpperCase();
});

Notes

  • Hooks are executed and then disposed of, if you're calling LogicfulTemplates.compileTemplate multiple times and want to re-use the hooks, you'll have to register them again.

Special components

<Magic> 🧙

This component allows you to perform magic that normally wouldn't work when using React regularly.

| prop | type | description | | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | compileLater | boolean OR number | Allows you to compile the component after everything else is compiled. If a boolean is passed, it will be rendered after every other regular component. If you have multiple <Magic compileLater> components, the regular order is maintained (i.e. based on which one is executed first). If a number is passed, the number will be considered as the priority of when to render the component, priority 1 is considered higher than priority 99. | | children | ReactNode | React children | | dangerouslySetInnerHTML | { __html: string } | Sets this element's innerHTML (which becomes outerHTML as <Magic> itself does not render an element) | | hoist | boolean | Allows you to hoist its children up a level, i.e. you could set this element's outerHTML |

Compiling a component later

This could be useful when you need to execute a function only at the end of compilation.

import { Magic } from 'logicful-templates';
import type { FunctionComponent } from 'react';

const Template: FunctionComponent<{}> = () => {
  return (
    <html>
      <head>
        <meta charSet='utf-8' />
      </head>
      <body>
        <div className="header">
          <Magic compileLater>
            {() => {
              executeSomeFunctionLast();
              const header = 'I am executed last';
              return <h1>{header}</h1>
            }}
          </Magic>
        </div>
        <div className="content">
          <p>Some text goes here.</p>
        </div>
      </body>
    </html>
  );
}

You can also specify a priority level by passing a number instead of true, the "compiler" will respect these values and execute the <Magic compileLater={number}> components in the correct order.

import { Magic } from 'logicful-templates';
import type { FunctionComponent } from 'react';

const Template: FunctionComponent<{}> = () => {
  return (
    <Magic compileLater={2}>
      {() => {
        executeSomeFunction();
        return <h1>I am executed 2nd!</h1>
      }}
    </Magic>
    <Magic compileLater={1}>
      {() => {
        executeSomeFunction();
        return <h1>I am executed 1st!</h1>
      }}
    </Magic>
  );
}

Notes

  • You are not required to pass a function as the <Magic> component's children, but they allow you to execute functions. Passing regular children (i.e. React elements) will work fine.
  • If you return another <Magic> component within a child render function of another <Magic> component, the priority number will only count for that level of <Magic> components. You normally wouldn't run into this scenario.

Hoisting a component / setting outerHTML

By itself <Magic hoist> isn't very useful, but combined with the dangerouslySetInnerHTML prop you can technically set this element's outerHTML. This could be useful if you need to inline non-standard bits into the DOM, for example a stylesheet, custom elements, or HTML comments.

import { Magic } from 'logicful-templates';
import type { FunctionComponent } from 'react';

const Template: FunctionComponent<{}> = () => {
  return (
    <>
      <Magic hoist dangerouslySetInnerHTML={{ __html: '<!-- A stylesheet below -->' }} />
      <style>
        <Magic
          hoist
          dangerouslySetInnerHTML={{
            __html: `
          body {
            background-color: linen;
          }

          h1 {
            color: maroon;
            margin-left: 40px;
          }
        `,
          }}
        />
      </style>
    </>
  );
};
<!-- A stylesheet below -->
<style>
  body {
    background-color: linen;
  }

  h1 {
    color: maroon;
    margin-left: 40px;
  }
</style>

<Custom>

Provides flexibility by allowing you to specify what the output tag name for an element will be, while also allowing you to specify any type of prop (or attribute) on the element.

| prop | type | description | | --------------- | ------------------- | --------------------------------------------------- | | tagName | string (lowercased) | A lowercased custom tag name for the custom element | | [key: string] | any | The <Custom> component accepts any type of props |

For example:

import { Custom } from 'logicful-templates';
import type { FunctionComponent } from 'react'

const MyComponent: FunctionComponent<{}> = () => {
  return (
    <Custom
      tagName='amp-img'
      alt='A view of the sea'
      src='/path/to/img'
      width={900}
      height={675}
      layout='responsive'
    />
  )
}
<amp-img
  alt="a view of the sea"
  src="/path/to/img"
  width="900"
  height="675"
  layout="responsive"
></amp-img>

You could even write an abstraction over the <Custom> component to provide better type hinting for the next person consuming your component. For example:

import { Custom } from 'logicful-templates';
import type { FunctionComponent } from 'react';

interface AmpImgProps {
  alt: string;
  src: string;
  width: number;
  height: number;
  layout: string;
}

const AmpImg: FunctionComponent<AmpImgProps> = (props) => {
  return <Custom tagName='amp-img' {...props} />;
};

<Comment>

Provides a way of adding HTML comments to the compiled output.

| prop | type | description | | ---------- | --------------------------- | -------------------------------- | | children | string OR number OR boolean | The content of the HTML comment. |

import { Comment } from 'logicful-templates';
import type { FunctionComponent } from 'react';

const MyComponent: FunctionComponent<{}> = () => {
  const input = 'World';

  return (
    <div>
      <Comment>Hello {input}</Comment>
    </div>
  )
};
<div>
  <!-- Hello World -->
</div>

Bring your own types

You could easily extend the default JSX types by defining your own types in a separate typings.d.ts file.

  • Note: If you're using ts-node to compile your templates, make sure to set { "ts-node": { "files": true } in your tsconfig.json, else you may run into TypeScript errors.
export {}

declare global {
  namespace JSX {
    interface IntrinsicElements {
      // Add a new element:
      'custom-element': {
        [key: string]: any;
      };
      // Or extend an existing React element:
      html: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement> & {
        amp4email: ""
      }
    }
  }
}