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 22, 2023
1 parent 3313601 commit aacd49a
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 55 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
205 changes: 150 additions & 55 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**. You can use it to add JSX support to Node.js without build tools. Its HTML 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 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 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,12 @@ 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 out of JSX-like function calls, 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 **requirement that children need to always be in an array**. If you already use a JSX compiler/transpiler, that's generally what JSX gets compiled down to.

```javascript
var Jsx = require("j6pack")
Expand All @@ -50,10 +50,10 @@ 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, by using J6Pack for transparent compiling/transpiling, see below.

### 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 +105,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 +144,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 +185,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 +198,93 @@ 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 is also a compiler/transpiler for JSX syntax. If you're looking to use JSX under Node.js, see the next section. This section covers the use of the standalone compiler.

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")

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:

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

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

### 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

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 ast = parse(`
var Jsx = require("j6pack")
var html = <h1 class="greeting">Hello, John!</h1>
`)

compile(ast)
```

### 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, `module` otherwise.

### 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 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 +304,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 @@ -316,25 +373,35 @@ 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.
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 three options:

```javascript
/** @jsx Jaysex */
var Jaysex = require("j6pack")
var html = <p>Hello, world!</p>
```
1. Use the `@jsx` pragma 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"}
```

3. Copy the contents of `j6pack/register` to your own project and invoke the `compile` function with options directly:

```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][#options].

### Renaming `Object.assign` for Spread Attributes
Spread attributes get compiled down to using `Object.assign`. For example, the following:
Expand All @@ -351,16 +418,44 @@ Jsx("input", Object.assign({}, defaults, {name: "email"}, attrs))

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.

### 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:
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`.


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
cat views/index_page.jsx | ./node_modules/.bin/j6pack
./node_modules/.bin/j6pack views/index_page.jsx
browserify --extension=jsx --transform j6pack/browserify
```

[eslint]: https://eslint.org/
[eslint-react]: https://github.com/yannickcr/eslint-plugin-react
Or add a `browserify` property to `package.json`:

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

### Setting Options
To set 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):


```sh
browserify --extension=jsx --transform [ j6pack/browserify --ecmaVersion 5 ]
```

Or via the `browserify` property in `package.json`:

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


License
Expand Down

0 comments on commit aacd49a

Please sign in to comment.