GithubHelp home page GithubHelp logo

ucontent's Introduction

µcontent

Build Status Coverage Status

sunflowers

Social Media Photo by Bonnie Kittle on Unsplash

📣 Community Announcement

Please ask questions in the dedicated forum to help the community around this project grow ♥


A micro SSR oriented HTML/SVG content generator, but if you are looking for a micro FE content generator, check µhtml out.

const {render, html} = require('ucontent');
const fs = require('fs');

const stream = fs.createWriteStream('test.html');
stream.once('open', () => {
  render(
    stream,
    html`<h1>It's ${new Date}!</h1>`
  ).end();
});

V2 Breaking Change

The recently introduced data helper could conflict with some node such as <object>, hence it has been replaced by the .dataset utility. Since element.dataset = object is an invalid operation, the sugar to simplify data- attributes is now never ambiguous and future-proof: <element .dataset=${...} /> it is.

This is aligned with µhtml and lighterhtml recent changes too.

API

  • a render(writable, what) utility, to render in a response or stream object, via writable.write(content), or through a callback, the content provided by one of the tags. The function returns the result of callback(content) invoke, or the the passed first parameter as is (i.e. the response or the stream). Please note this helper is not mandatory to render content, as any content is an instance of String, so that if you prefer to render it manually, you can always use directly content.toString() instead, as every tag returns a specialized instance of String. This API doesn't set any explicit headers for response objects based on what.
  • a html tag, to render HTML content. Each interpolation passed as layout content, can be either a result from html, css, js, svg, or raw tag, as well as primitives, such as string, boolean, number, or even null or undefined. The result is a specialized instance of String with a .min() method to produce eventually minified HTML content via html-minifier. All layout content, if not specialized, will be safely escaped, while attributes will always be escaped to avoid layout malfunctions.
  • a svg tag, identical to the html one, except minification would preserve any self-closing tag, as in <rect />.
  • a css tag, to create CSS content. Its interpolations will be stringified, and it returns a specialized instance of String with a .min() method to produce eventually minified CSS content via csso. If passed as html or svg tag interpolation content, .min() will be automatically invoked.
  • a js tag, to create JS content. Its interpolations will be stringified, and it returns a specialized instance of String with a .min() method to produce eventually minified JS content via terser. If passed as html or svg tag interpolation content, .min() will be automatically invoked.
  • a raw tag, to pass along interpolated HTML or SVG values any kind of content, even partial one, or a broken, layout.

Both html and svg supports µhtml utilities but exclusively for feature parity (html.for(...) and html.node are simply aliases for the html function).

Except for html and svg tags, all other tags can be used as regular functions, as long as the passed value is a string, or a specialized instance.

This allow content to be retrieved a part and then be used as is within these tags.

import {readFileSync} from 'fs';
const code = js(readFileSync('./code.js'));
const style = css(readFileSync('./style.css'));
const partial = raw(readFileSync('./partial.html'));

const head = title => html`
  <head>
    <title>${title}</title>
    <style>${style}</style>
    <script>${code}</script>
  </head>
`;

const body = () => html`<body>${partial}</body>`;

const page = title => html`
  <!doctype html>
  <html>
    ${head(title)}
    ${body()}
  </html>
`;

All pre-generated content can be passed along, automatically avoiding minification of the same content per each request.

// will be re-used and minified only once
const jsContent = js`/* same JS code to serve */`;
const cssContent = css`/* same CSS content to serve */`;

require('http')
  .createServer((request, response) => {
    response.writeHead(200, {'content-type': 'text/html;charset=utf-8'});
    render(response, html`
      <!doctype html>
      <html>
        <head>
          <title>µcontent</title>
          <style>${cssContent}</style>
          <script>${jsContent}</script>
        </head>
      </html>
    `.min()).end();
  })
  .listen(8080);

If one of the HTML interpolations is null or undefined, an empty string will be placed instead.

Note: When writing to stream objects using the render() API make sure to call end on it

Production: HTML + SVG Implicit Minification

While both utilities expose a .min() helper, repeated minification of big chunks of layout can be quite expensive.

As the template literal is the key to map updates, which happen before .min() gets invoked, it is necessary to tell upfront if such template should be minified or not, so that reusing the same template later on, would result into a pre-minified set of chunks.

In order to do so, html and svg expose a minified boolean property, which is false by default, but it can be switched to true in production.

import {render, html, svg} from 'ucontent';

// enable pre minified chunks
const {PRODUCTION} = process.env;
html.minified = !!PRODUCTION;
svg.minified = !!PRODUCTION;

const page = () => html`
  <!doctype html>
  <html>
    <h1>
      This will always be minified
    </h1>
    <p>
      ${Date.now()} + ${Math.random()}
    </p>
  </html>
`;
// note, no .min() necessary

render(response, page()).end();

In this way, local tests would have a clean layout, while production code will always be minified, where each template literal will be minified once, instead of each time .min() is invoked.

Attributes Logic

  • as it is for µhtml too, sparse attributes are not supported: this is ok attr=${value}, but this is wrong: attr="${x} and ${y}".
  • all attributes are safely escaped by default.
  • if an attribute value is null or undefined, the attribute won't show up in the layout.
  • aria=${object} attributes are assigned hyphenized as aria-a11y attributes. The role is passed instead as role=....
  • style=${css...} attributes are minified, if the interpolation value is passed as css tag.
  • .dataset=${object} setter is assigned hyphenized as data-user-land attributes.
  • .contentEditable=${...}, .disabled=${...} and any attribute defined as setter, will not be in the layout if the passed value is null, undefined, or false, it will be in the layout if the passed value is true, it will contain escaped value in other cases. The attribute is normalized without the dot prefix, and lower-cased.
  • on...=${'...'} events passed as string or passed as js tag will be preserved, and in the js tag case, minified.
  • on...=${...} events that pass a callback will be ignored, as it's impossible to bring scope in the layout.

Benchmark

Directly from pelo project but without listeners, as these are mostly useless for SSR.

Rendering a simple view 10,000 times:

node test/pelo.js
tag time (ms)
ucontent 117.668ms
pelo 129.332ms

How To Live Test

Create a test.js file in any folder you like, then npm i ucontent in that very same folder.

Write the following in the test.js file and save it:

const {render, html} = require('ucontent');

require('http').createServer((req, res) => {
  res.writeHead(200, {'content-type': 'text/html;charset=utf-8'});
  render(res, html`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>ucontent</title>
    </head>
    <body>${html`
      <h1>Hello There</h1>
      <p>
        Thank you for trying µcontent at ${new Date()}
      </p>
    `}</body>
    </html>
  `)
  .end();
}).listen(8080);

You can now node test.js and reach localhost:8080, to see the page layout generated.

If you'd like to test the minified version of that output, invoke .min() after the closing </html> template tag:

  render(res, html`
    <!DOCTYPE html>
    <html lang="en">
      ...
    </html>
  `.min()
  ).end();

You can also use html.minified = true on top, and see similar results.

API Summary Example

import {render, css, js, html, raw} from 'ucontent';

// turn on implicit html minification (production)
html.minified = true;

// optionally
// svg.minified = true;

render(content => response.end(content), html`
  <!doctype html>
  <html lang=${user.lang}>
    <head>
      <!-- dynamic interpolations -->
      ${meta.map(({name, content}) =>
                    html`<meta name=${name} content=${content}>`)}
      <!-- explicit CSS minification -->
      <style>
      ${css`
        body {
          font-family: sans-serif;
        }
      `}
      </style>
      <!-- explicit JS minification -->
      <script>
      ${js`
        function passedThrough(event) {
          console.log(event);
        }
      `}
      </script>
    </head>
    <!-- discarded callback events -->
    <body onclick=${() => ignored()}>
      <div
        class=${classes.join(' ')}
        always=${'escaped'}
        .contentEditable=${false}
        .dataset=${{name: userName, id: userId}}
        aria=${{role: 'button', labelledby: 'id'}}
        onmouseover=${'passedThrough.call(this,event)'}
      >
        Hello ${userName}!
        ${raw`<some> valid, or even ${'broken'}, </content>`}
      </div>
    </body>
  </html>
`);

ucontent's People

Contributors

leo-ar avatar r0mflip avatar webreflection avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ucontent's Issues

Render to static HTML

Hi, Andrea!

I am just playing around with ucontent.

And I just have a question:
What is the most efficient way to render via ucontent to static HTML?

Currently I use this:

const { html } = require('ucontent');
const fs = require('fs');

var stream = fs.createWriteStream("index.html");
stream.once('open', function () {
  stream.write(Buffer.from(html`
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ucontent</title>
  </head>
  <body>${html`
    <h1>Hello There</h1>
    <p>
      Thank you for trying µcontent at ${new Date()}
    </p>
  `}</body>
  </html>
`));
  stream.end();
});

Attribute spread

Hi,

Thank you for your work.
Does html support attribute spread?

Thank you

External HTML components escaped

Hi! 👋
I'm working on a component helper library to be shared across multiple projects, which uses ucontent to generate server-side markup for each component. However, the two separate instances of ucontent (component lib & project) are incompatible for rendering markup. The component markup is escaped as text instead of live html (but works properly if I copy/paste the component library source into the project repo and use the same ucontent instance).

I believe the problem is caused by the prefix for uparser in utils.js using Date.now(). Is this timestamp necessary for proper functionality?

I've cloned the repo here and removed the timestamp, which seems to fix things. I'm cautious though, since I'm not sure if the internal parser depends on a unique prefix for proper caching / whatnot.

Happy to create a demonstration repo if that helps.

Thanks for your help and your great collection of libraries!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.