Skip to content
JavaScript library to render JSX to HTML on the web or on Node.js. Works with Express.js. Minimal with no React dependency.
JavaScript Makefile
Branch: master
Clone or download
Latest commit 9340b81 Jun 6, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
examples Add DOCTYPE to example server. Jun 6, 2019
lib Remove "HTML" mention from Markup. Jun 6, 2019
test Add XML support. Jun 6, 2019
.editorconfig Stereotyping. Mar 9, 2019
.gitignore Stereotyping. Mar 9, 2019
.npmignore Add Travis CI. Jun 6, 2019
.travis.yml Add Travis CI. Jun 6, 2019
CHANGELOG.md Release v1.0.0. Jun 6, 2019
LICENSE Stereotyping. Mar 9, 2019
Makefile Add HTTP server example. Mar 9, 2019
README.md Use Html.prototype.toString in README example. Jun 6, 2019
compile.js Stereotyping. Mar 9, 2019
express.js Prepend HTML doctype only when requested. Jun 6, 2019
html.js Add XML support. Jun 6, 2019
package.json Release v1.0.0. Jun 6, 2019
register.js Stereotyping. Mar 9, 2019
xml.js Add XML support. Jun 6, 2019

README.md

J6Pack.js

NPM version Build status

J6Pack.js is a JavaScript library that renders JSX to HTML. It's also usable from Node.js to render JSX on the server side and can hook into Express.js view rendering. It's minimal and does not depend on React.

J6Pack.js depends on JsxTransform for parsing and compiling JSX to function calls, but you're welcome to use compatible parsers and hook .jsx file handling into Node.js yourself.

Installing

npm install j6pack

J6Pack.js follows semantic versioning, so feel free to depend on its major version with something like >= 1.0.0 < 2 (a.k.a ^1.0.0).

Using

For just generating HTML out of JSX function calls, require J6Pack.js and call it in the style of React.createElement or equivalent JSX-functions, with the exception that children need to always be in an array.

var Jsx = require("j6pack")

var html = Jsx("p", null, [
  "Hello, ",
  Jsx("em", null, ["world"]),
  "!"
])

String(html) // => "<p>Hello, <span>world</span>!</p>"

The html variable itself is an instance of Jsx.Html with valueOf and toString methods that return the HTML for the entire tree. The use of a value object over a string is mostly an implementation requirement of the way JSX compilers work. It does however permit you to differentiate between unescaped strings and escaped HTML via instanceof should you need to:

Jsx("p", null, ["Hello, world!"]) instanceof Jsx.Html // => true
<p>Hello, world!</p> instanceof Jsx.Html // => true

To use the JSX syntax (<p>Hello, {name}!</p>) in JavaScript on Node.js, read on. Note, however, that for now you need to name the export of J6Pack.js to Jsx as the JSX compiler is hard-coded to use that. If you wish it to be customizable, please let me know.

XML

J6Pack.js by default renders HTML5 compatible HTML. If you'd like to use it to render generic XML, for example to render an Atom feed, you can require the XML variant:

var Jsx = require("j6pack/xml")

var atom = <feed xmlns="http://www.w3.org/2005/Atom">
  <id>http://example.com</id>
  <title>My Blog</title>

  {articles.map(function(article) {
    return <entry>
      <id>{article.url}</id>
      <title>{article.title}</title>
      <content type="text">{article.text}</content>
    </entry>
  })}
</feed>

Note that unfortunately the JsxTransform library J6Pack.js uses for converting JSX to JavaScript doesn't support XML namespace aliases at the moment. Namespace alias are when you set the namespace with xmlns:atom="http://www.w3.org/2005/Atom" and use tags in the style of <atom:feed>. I will probably eventually rewrite JsxTransform to support that. Alternatively, you could see about using another transformation library (Babel.js, perhaps) and just using J6Pack's rendering functions.

Node.js

To add support for JSX syntax to Node.js, require j6pack/register before starting the app:

node --require j6pack/register app.js

Or require it before you load .jsx files:

require("j6pack/register")

Then you can render HTML from within your request handlers:

var Jsx = require("j6pack")
var Http = require("http")

function handleRequest(_req, res) {
  res.setHeader("Content-Type", "text/html; charset=utf-8")

  res.end(<html>
    <head><title>J6Pack Test Server</title></head>
    <body><p>Hello, world!</p></body>
  </html>.toString("doctype"))
}

Http.createServer(handleRequest).listen(process.env.PORT || 3000)

The same example code is in examples/server.jsx which you can run via make examples/server.jsx and view on http://localhost:3000 by default.

Express.js

J6Pack.js comes with explicit support for the Express.js web framework templates. To add .jsx template support to Express.js, set it as an engine:

var Express = require("express")
var app = Express()
app.engine(".jsx", require("j6pack/express"))

You can then render a JSX component through res.render. As its attributes it gets the sum of global app.locals, request's res.locals and the attributes you pass to res.render, much just like a Jade/Pug template would:

app.get("/", function(_req, res) {
  res.render("index_page.jsx", {greeting: "Hello, world!"})
})

An example template in views/index_page.jsx:

var Jsx = require("j6pack")

module.exports = function(attrs) {
  return <html>
    <head><title>J6Pack Test Express Template Server</title></head>
    <body><p>{attrs.getting}</p></body>
  </html>
}

Note that you don't need to wrap the JSX in String as you did elsewhere. That's handled for you by J6Pack.js's integration with Express.js.

If you'd rather have your routes render HTML directly without extracting templates to a separate file, you can do the same as with the plain HTTP server above. You don't even need to set the view engine parameter as this will be bypassing Express.js's templating entirely:

var Jsx = require("j6pack")
var Http = require("http")
var Express = require("express")
var app = Express()

app.get("/", function(_req, res) {
  res.send(<html>
    <head><title>J6Pack Test Express Server</title></head>
    <body><p>Hello, world!</p></body>
  </html>.toString("doctype"))
})

Http.createServer(handleRequest).listen(process.env.PORT || 3000)

Note we're using res.send. That sets the Content-Type header automatically that we previously had to set ourselves with res.setHeader.

If you want Express to default to the .jsx extension for templates, set the view engine property to .jsx:

var Express = require("express")
var app = Express()
app.set("view engine", ".jsx")

You can then leave out the .jsx extension from your res.render call:

app.get("/", function(_req, res) {
  res.render("index_page", {greeting: "Hello, world!"})
})

DOM Attributes and Properties

J6Pack.js renders JSX to HTML with minimal transformations, so use HTML attribute names, not DOM properties. That is, to set the tag's class attribute, use <p class="greeting">Hello, world!</p> rather than className as you would with React. Same goes for elements' onclick, <label>s' for, <input>s' readonly and so on.

Interpolating HTML (like innerHTML)

Should you need to interpolate HTML into JSX, you can't use the innerHTML property (or React's dangerouslySetInnerHTML) as those live only in the DOM world, not in HTML. Instead, use the Jsx.html function to inject your HTML into otherwise escaped values:

var Jsx = require("j6pack")
var html = <p>Hello, {Jsx.html("<em>world</em>")}!</p>
String(html) // => "<p>Hello, <em>world</em>!</p>"

Custom Elements (Functional Components)

Just like React or other virtual DOM implementations, J6Pack.js supports custom elements. Behind the scenes a custom element is really just a function that gets invoked with two arguments — an object of attributes and an array of children. Both can be null if they were not given, so beware.

function Page(attrs, children) {
  return <html>
    <head><title>{attrs.title}</title></head>
    <body>{children}</body>
  </html>
}

var html = <Page title="Test Page">
  <p>Hello, world!</p>
</Page>

Returning Multiple Elements (React Fragments)

Occasionally you may want a custom element to return multiple elements without wrapping them in yet another element. You can do that in two ways.

Return an array of elements:

var Jsx = require("j6pack")

function Input(attrs) {
  return [
    <label>{attrs.label}</label>,
    <input type={attrs.type} value={attrs.value} />
  ]
}

Alternatively, wrap them in a Jsx.Fragment element, akin to React.Fragment:

var Jsx = require("j6pack")
var Fragment = Jsx.Fragment

function Input(attrs) {
  return <Fragment>
    <label>{attrs.label}</label>
    <input type={attrs.type} value={attrs.value} />
  </Fragment>
}

Then use it as you would any other custom element:

var html = <div>
  <Input label="Name" value="John" />
  <br />
  <Input label="Age" type="number" value={42} />
</div>

JsxTransform, the library J6Pack.js is using for compiling JSX expressions to function calls, unfortunately doesn't support React's new <> syntax for fragments. It also doesn't yet support referring to member properties (Foo.Bar) in tag names (<Foo.Bar>), hence the need to assign Jsx.Fragment to the Fragment variable above.

Executable

J6Pack.js comes with a simple executable, j6pack that you can use to precompile JSX, perhaps for testing. After installing J6Pack.js, invoke it with ./node_modules/.bin/j6pack or from bin/j6pack from this repository:

cat views/index_page.jsx | ./node_modules/.bin/j6pack
./node_modules/.bin/j6pack views/index_page.jsx

ESLint

If you use ESLint with the ESLint React plugin to lint your JavaScript and are getting an error about missing React when using JSX, you have to specify a pragma to inform it of the JSX function used:

/** @jsx Jsx */
var Jsx = require("j6pack")
var html = <p>Hello, world!</p>

License

J6Pack.js is released under a Lesser GNU Affero General Public License, which in summary means:

  • You can use this program for no cost.
  • You can use this program for both personal and commercial reasons.
  • You do not have to share your own program's code which uses this program.
  • You have to share modifications (e.g. bug-fixes) you've made to this program.

For more convoluted language, see the LICENSE file.

About

Andri Möll typed this and the code.
Monday Calendar supported the engineering work.

If you find J6Pack.js needs improving, please don't hesitate to type to me now at andri@dot.ee or create an issue online.

You can’t perform that action at this time.