Skip to content

Commit

Permalink
Update README and CHANGELOG.
Browse files Browse the repository at this point in the history
  • Loading branch information
moll committed Jul 26, 2023
1 parent 3313601 commit 584c973
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 66 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,17 @@
## Unreleased
- Switches from using [JsxTransform][jsx-transform] to implementing a custom JSX compiler with the help of [Acorn][acorn], a JavaScript parsing library.

The result is almost byte-for-byte identical with the exception of a few improvements around closing-brace position. Just like JsxTransform, J6Pack retains all whitespace in the source file, so all reported line numbers match.

Switching to Acorn also adds support for ECMAScript 14 (2023) and beyond.

- Adds a [Browserify][browserify] transform.
- Adds a way to set compilation options when using `j6pack/regiser` via setting the `options` property of its export.

[acorn]: https://www.npmjs.com/package/acorn
[jsx-transform]: https://www.npmjs.com/package/jsx-transform
[browserify]: https://browserify.org

## 1.1.0 (Jan 11, 2021)
- Escapes ampersands (`&`) in HTML and XML attributes.
Previous behavior permitted using HTML entities in attributes, but it's safer to opt-in to that where necessary with `Jsx.html`:
Expand Down
270 changes: 205 additions & 65 deletions README.md
Expand Up @@ -3,9 +3,9 @@ J6Pack.js
[![NPM version][npm-badge]](https://www.npmjs.com/package/j6pack)
[![Build status][build-badge]](https://github.com/moll/js-j6pack/actions/workflows/node.yaml)

J6Pack.js is a JavaScript library that transpiles **JSX to JavaScript** _and_ can separately render **JSX to HTML**. It comes with a [Browserify][browserify] transform out of the box. It's also usable from Node.js to **render JSX on the server side** and can hook into **[Express.js][express] view rendering**. J6Pack **does not depend on React nor implements a virtual DOM**. The HTML it renders is just text, though with interpolated values securely escaped.
J6Pack.js is a JavaScript library that compiles **JSX to JavaScript** _and_ can then render the **JSX to HTML or XML**. You can use it to add JSX support to Node.js without build tools. Its HTML/XML rendering is also compatible with any web framework, though it's got builtin support for **[Express.js][express]**. You can even use it client-side as the HTML/XML rendering is ECMAScript 5 compatible. There's even a [Browserify][browserify] plugin handy for bundling.

To use JSX on the backend with Node.js, you don't need an external build tool. Just let J6Pack.js handle the JSX translation transparently as you `require` or import modules.
J6Pack **does not depend on React nor implements a virtual DOM**. The HTML/XML it renders is just text, though with interpolated values securely escaped.

J6Pack.js defaults to using [Acorn][acorn] for parsing JSX — supporting ECMAScript 14 (2023) and beyond — but you're welcome to use any [ESTree](https://github.com/estree/estree) compatible parser and invoke J6Pack.js's compiler directly.

Expand All @@ -24,12 +24,27 @@ Installing
npm install j6pack
```

J6Pack.js follows [semantic versioning](http://semver.org), so feel free to depend on its major version with something like `>= 1.0.0 < 2` (a.k.a `^1.0.0`).
J6Pack.js follows [semantic versioning](http://semver.org), so feel free to depend on its major version with something like `>= 1 < 2` (a.k.a `^1`).


Using J6Pack to Render HTML
---------------------------
For just generating HTML out of JSX function calls, require J6Pack.js and call it in the style of [`React.createElement`](https://react.dev/reference/react/createElement) or [equivalent JSX-functions](https://reactjs.org/docs/jsx-in-depth.html), with the exception that children need to always be in an array.
Rendering HTML
--------------
For rendering HTML, import J6Pack.js and call it in the style of [`React.createElement`](https://react.dev/reference/react/createElement) or [equivalent JSX-functions](https://reactjs.org/docs/jsx-in-depth.html), with the additional **requirement that children always be in an array**. Generally that's how a JSX compiler/transpiler would render the function calls.

For example, setting the factory function to `Jsx` via the `@jsx` pragma (most JSX compilers support this), using J6Pack.js would look like:

```javascript
/** @jsx Jsx */
var Jsx = require("j6pack")

var html = <p class="greeting">
Hello, <em>world</em>!
</p>

String(html) // => "<p class=\"greeting\">Hello, <em>world</em>!</p>"
```

The above should get compiled to the following. You're welcome to always call `Jsx` manually, too, if you don't like the JSX syntax:

```javascript
var Jsx = require("j6pack")
Expand All @@ -43,17 +58,17 @@ var html = Jsx("p", {class: "greeting"}, [
String(html) // => "<p class=\"greeting\">Hello, <em>world</em>!</p>"
```

The `html` variable above 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:
The `html` variable in both examples 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:

```javascript
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, see below.
To use the JSX syntax (`<p>Hello, {name}!</p>`) on Node.js without an external compiler tool, see the section on ["Using JSX with Node.js"](#using-jsx-with-nodejs).

### 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](https://en.wikipedia.org/wiki/Atom_(Web_standard)), you can require the XML variant:
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](https://en.wikipedia.org/wiki/Atom_(Web_standard)), you can require the XML variant from `j6pack/xml`:

```javascript
var Jsx = require("j6pack/xml")
Expand Down Expand Up @@ -105,10 +120,10 @@ Jsx("p", null, ["Hello, world!"]) instanceof Jsx.Xml // => true
```

### 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.
J6Pack.js renders 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](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)) 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:
Should you need to interpolate HTML into the output, you can't use the `innerHTML` property (or React's [dangerouslySetInnerHTML](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)) 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:

```javascript
var Jsx = require("j6pack")
Expand Down Expand Up @@ -144,7 +159,9 @@ var html = <Page title="Test Page">
</Page>
```

### Returning Multiple Elements (React Fragments)
If you use another JSX compiler/transpiler than J6Pack.js, configure it to call component functions directly, rather than passing them to the factory function.

### Returning Multiple Elements (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:
Expand Down Expand Up @@ -183,6 +200,8 @@ var html = <div>
</div>
```

If you need to configure your external JSX compiler/transpiler for fragments, you can set it to emit fragments either as JavaScript arrays or as invocations of `Jsx.Fragment` like regular components (children in the 2nd argument as an array).

### ESLint
If you use [ESLint][eslint] with the [ESLint React plugin][eslint-react] to lint your JavaScript and are getting an error about [missing `React` when using JSX](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md), you may have to specify a pragma to inform ESLint of the JSX function used:

Expand All @@ -194,23 +213,140 @@ var html = <p>Hello, world!</p>

A sufficiently new version of [ESLint React plugin][eslint-react] seems to also have a `pragma` setting to set the factory function once for the entire project.

[eslint]: https://eslint.org/
[eslint-react]: https://github.com/yannickcr/eslint-plugin-react


Compiling JSX to JavaScript
---------------------------
In addition to rendering HTML, J6Pack.js also contains a compiler/transpiler for JSX syntax. This section covers the use of the standalone compiler, so if you're looking to use JSX while writing your app (for Node.js), see the section on ["Using JSX with Node.js"](#using-jsx-with-nodejs).

To compile a string of JavaScript with JSX into plain JavaScript, import the J6Pack.js compiler and invoke it with the source code

```javascript
var compile = require("./compiler")

Using J6Pack to Render JSX to JavaScript
----------------------------------------
### Node.js
To add support for JSX syntax to Node.js, require `j6pack/register` before starting the app:
var js = compile(`
var Jsx = require("j6pack")
var html = <Greeting name="John" />
function Greeting(attrs) {
return <h1 class="greeting">Hello, {attrs.name}!</h1>
}
`)
```

The `js` variable should then include the compiled/transpiled output as a string:

```js
var Jsx = require("j6pack")
var html = Greeting({name: "John"})

function Greetng(attrs) {
return Jsx("h1", {class: "greeting"}, ["Hello, ", attrs.name, "!"])
}
```

### Compiler Options
The compiler has a few options to customize its output.

Option | Description
-------|------------
`factory` | The function called for creating elements.<br>Called with the tag name, attributes object or `null`, and an array of children.<br>Defaults to `Jsx`.
`fragmentFactory` | The function called for `<>…</>` JSX syntax.<br>Called with `null` and an array of children.<br>Set to `null` if want a plain JavaScript array instead of a function call.<br>Defaults to `null`.
`componentFactory` | The function called for component element (tags whose name starts with a capitalized letter).<br>Called with the component variable, attributes object or `null`, and an array of children.<br>Set to `null` if you want the component variable called directly with attributes and children.<br>Defaults to `null`.
`assign` | The function used for merging spread attributes (`<div {...attrs} class="foo" />`).<br>Defaults to `Object.assign`.
`ecmaVersion` | Set or limit the JavaScript version for parsing. Useful for ensuring you don't accidentally use newer JavaScript syntax in your source files.<br>[Acorn][acorn], the parser J6Pack.js uses by default, supports versions from 3–14 and beyond.<br>Defaults to `"latest"`.
`sourceType` | Set to `script` or `module` to configure support for `import` and `export` declarations.<br>Defaults to `script` if `ecmaVersion` is 3 or 5 and `module` otherwise.

You can pass the compiler options to `compile` as a second argument:

```javascript
var compile = require("./compiler")

var js = compile(`
var Jaysex = require("j6pack")
var html = <Greeting name="John" />
function Greeting(attrs) {
return <h1 class="greeting">Hello, {attrs.name}!</h1>
}
`, {factory: "Jaysex"})
```

### Renaming the `Jsx` Function
By default the compiled JavaScript expects the name of the JSX-render (factory) function to be `Jsx`. That's why all the examples here assign `require("j6pack")` to `Jsx`. If you want to use a different function name, you've got two options:

1. Use the `@jsx` pragma to set the factory function:

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

2. Set the compiler option `factory`.

### Renaming `Object.assign` for Spread Attributes
Spread attributes get compiled down to using `Object.assign`. For example, the following:

```javascript
<input {...defaults} name="email" {...attrs} />
```

Gets compiled down to:

```javascript
Jsx("input", Object.assign({}, defaults, {name: "email"}, attrs))
```

If you wish to not depend on `Object.assign`, you can use the `assign` compiler option to replace `Object.assign` with a function name of your own.

Both the HTML (`require("j6pack/html")`) and XML (`require("j6pack/xml")`) renderers have an `assign` export with a helper function that shallow-merges objects. If you can't depend on Object.assign being available (e.g. in old browsers) or you dislike its ignoring of inherited properties, you're welcome to set the `assign` option to `Jsx.assign`.

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

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

### Using a Different JavaScript Parser
J6Pack.js defaults to using [Acorn][acorn] for parsing JSX — supporting ECMAScript 14 (2023) and beyond — but you're welcome to use any [ESTree](https://github.com/estree/estree) compatible parser that also supports JSX AST nodes.

For example, use of [Esprima][esprima] should be something like:

```js
var parse = require("esprima").parse
var compile = require("j6pack/compiler").compile

var ast = parse(`
var Jsx = require("j6pack")
var html = <h1 class="greeting">Hello, John!</h1>
`)

compile(ast)
```

[esprima]: https://esprima.org


Using JSX with Node.js
----------------------
To add transparent support for importing JSX source files to Node.js, require `j6pack/register` before starting the app:

```sh
node --require j6pack/register app.js
```

Or require it in your main entry file before you load `.jsx` files:
Or `require` it in your main entry file before you load `.jsx` files:

```javascript
require("j6pack/register")
```

Then you can write JSX anywhere in `.jsx` files and render HTML, for example, from within your request handlers:
Then, combined with J6Pack.js's HTML rendering, you can use JSX to respond with HTML from within your request handlers:

```javascript
var Jsx = require("j6pack")
Expand All @@ -230,23 +366,6 @@ 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.

### Browserify
To have [Browserify][browserify] use J6Pack.js for precompiling JSX files to JavaScript, pass `j6pack/browserify` as a transform when invoking Browserify:

```sh
browserify --extension=jsx --transform j6pack/browserify
```

Or add a `browserify` property to `package.json`:

```json
{
"browserify": {
"transform": ["j6pack/browserify"]
}
}
```

### Express.js
J6Pack.js comes with explicit support for the [Express.js][express] web framework [templates](https://expressjs.com/en/guide/using-template-engines.html). To add `.jsx` template support to Express.js, set it as an engine:

Expand Down Expand Up @@ -315,52 +434,73 @@ app.get("/", function(_req, res) {
})
```

### Renaming the `Jsx` Function
By default the transpiled JavaScript expects the name of the JSX-render (factory) function to be `Jsx`. That's why all the examples here assign `require("j6pack")` to `Jsx`. If you want to use a different function name, feel free to either use the `@jsx` pragma or make a local copy of `j6pack/register` for use in Node.js.
### Setting Options
To set the JSX [compiler options](#compiler-options) (for example, to rename the JSX-render (factory) function), you've got three options:

```javascript
/** @jsx Jaysex */
var Jaysex = require("j6pack")
var html = <p>Hello, world!</p>
```
1. Use the `@jsx` pragma in your JSX files to set the factory function:

Or run the following JavaScript at the start of the your app (just like `require`ing `j6pack/register` does):
```javascript
/** @jsx Jaysex */
var Jaysex = require("j6pack")
var html = <p>Hello, world!</p>
```

```javascript
var Fs = require("fs")
var compile = require("./compiler")
2. Set the `options` on the export of `j6pack/register` before you `require` any `.jsx` files:

require.extensions[".jsx"] = function(module, path) {
var source = Fs.readFileSync(path, "utf8")
module._compile(compile(source, {factory: "Jaysex"}), path)
}
```
```javascript
require("j6pack/register").options = {factory: "Jaysex"}
```

### Renaming `Object.assign` for Spread Attributes
Spread attributes get compiled down to using `Object.assign`. For example, the following:
3. Copy the contents of `j6pack/register` to your own project and invoke the `compile` function with options directly:

```javascript
<input {...defaults} name="email" {...attrs} />
```javascript
var Fs = require("fs")
var compile = require("./compiler")

require.extensions[".jsx"] = function(module, path) {
var source = Fs.readFileSync(path, "utf8")
module._compile(compile(source, {factory: "Jaysex"}), path)
}
```

See above for a [list of all options](#compiler-options).


Compiling JSX to JavaScript with Browserify
-------------------------------------------
To have [Browserify][browserify] use J6Pack.js for precompiling JSX files to JavaScript, pass `j6pack/browserify` as a transform when invoking Browserify:

```sh
browserify --extension=jsx --transform j6pack/browserify
```

Gets compiled down to:
Or add a `browserify` property to `package.json`:

```javascript
Jsx("input", Object.assign({}, defaults, {name: "email"}, attrs))
```json
{
"browserify": {
"transform": ["j6pack/browserify"]
}
}
```

If for some reason you wish to not depend on `Object.assign`, you can use the `assign` option when compiling to replace `Object.assign` with a function name of your own. Works the same as above with changing the `factory` function name.
### Setting Options
To set the JSX [compiler options](#compiler-options), use the Browserify transform option syntax. For example, to limit J6Pack.js's JavaScript parser to ECMAScript 5 (to prevent accidentally using newer JavaScript syntax in your source files):

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

```sh
cat views/index_page.jsx | ./node_modules/.bin/j6pack
./node_modules/.bin/j6pack views/index_page.jsx
browserify --extension=jsx --transform [ j6pack/browserify --ecmaVersion 5 ]
```

[eslint]: https://eslint.org/
[eslint-react]: https://github.com/yannickcr/eslint-plugin-react
Or via the `browserify` property in `package.json`:

```json
{
"browserify": {
"transform": [["j6pack/browserify", {"ecmaVersion": "5"}]]
}
}
```


License
Expand Down

0 comments on commit 584c973

Please sign in to comment.