Plugin for SWC to enable JSX/TSX for Inferno
This plugin transforms JSX and TSX code in your projects to Inferno compatible virtual DOM.
It is recommended to use this plugin for compiling JSX for inferno. It is different to other JSX plugins, because it outputs highly optimized inferno specific newVNode calls. This plugin also checks children shape during compilation stage to reduce overhead from runtime application.
The plugin generates the same code as babel-plugin-inferno, and its
test suite is ported to this repository (tests/babel_plugin_inferno).
Version 10 of the plugin compiles JSX for Inferno 10. It calls newVNode, newComponentVNode, newFragment and
newTextVNode, and writes the flags of each vNode, including the shape of its children, as one number. For example
<div>Hello</div> becomes newVNode(3, "div", null, "Hello"): HtmlElement (1) and HasTextChildren (2).
For Inferno 9 and older, use version 3.x of the plugin. Inferno 10 numbers its vNode flags differently, so code compiled by version 3.x has to be compiled again for Inferno 10.
From version 10 on, the major version of the plugin matches the major version of Inferno: use plugin 10.x with
Inferno 10.x, plugin 11.x with Inferno 11.x, and so on. The plugin does not declare inferno as a peer dependency, so
the package manager does not check this for you. See the
Inferno 10 migration guide.
The flags the plugin writes are the VNodeFlags of inferno-vnode-flags 10:
| Flag | Value |
|---|---|
ComponentUnknown |
0 |
HtmlElement |
1 |
HasTextChildren |
2 |
HasNonKeyedChildren |
4 |
HasVNodeChildren |
8 |
HasInvalidChildren |
16 |
HasKeyedChildren |
32 |
SvgElement |
64 |
Fragment |
256 |
InputElement |
512 |
TextareaElement |
2048 |
SelectElement |
4096 |
ContentEditable |
131072 |
Children that are only known at runtime, such as {expression}, get no child bit and are normalized by Inferno.
npm i --save-dev swc-plugin-infernoInstall the major version that matches your inferno version, see Inferno versions.
Add swc-plugin-inferno to .swcrc configuration
Enable jsc.parser.jsx and set swc-plugin-inferno into jsc.experimental.plugins
For rest of the settings see: https://swc.rs/docs/configuration/compilation
{
"jsc": {
"experimental": {
"plugins": [
["swc-plugin-inferno", {
"pure": true // Enable or disable /*#__PURE__*/ annotations (default: true)
}]
]
}
}
}Unknown plugin options are rejected with an error. The options are:
| Option | Default | Description |
|---|---|---|
pure |
true |
Add /*#__PURE__*/ annotations, see below. |
importSource |
"inferno" |
The module the helpers are imported from, see Options. |
development |
false |
Enables fast refresh together with refresh. On its own it changes nothing. |
refresh |
off | Fast refresh: true, or { "refreshReg": "$RefreshReg$", "refreshSig": "$RefreshSig$", "emitFullSignatures": false }. Only applies when development is true. |
uselessFlags |
"warn" |
What to do about useless flags: "warn", "error" or "off". |
With "pure": true the plugin adds /*#__PURE__*/ to the calls it generates from JSX, and also to hand-written calls
of Inferno factories such as forwardRef, createRef, createPortal, newVNode and the deprecated createVNode
imported from inferno or from importSource. Minifiers remove these calls when their result is unused, so don't call
them only for their side effects. normalizeProps is annotated only when its argument is a freshly created vNode,
because it mutates the vNode passed to it.
With "development": true and "refresh": true the plugin registers components for hot reloading like
react-refresh/babel: components are passed to $RefreshReg$, and the hooks of a component to a signature created by
$RefreshSig$. The functions are renamed with refreshReg and refreshSig. Your hot reloading runtime has to define
them. Any call of a function named use followed by a capital letter counts as a hook, for example useLoaderData of
inferno-router. A // @refresh reset comment in a file remounts its components on every edit.
To use SWC with Webpack install swc-loader and add it to the Webpack configuration
{
mode: 'development',
entry: './src/index.js',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
// `.swcrc` can be used to configure swc
loader: 'swc-loader',
},
}
]
}
}// Render a simple div
Inferno.render(<div></div>, container);
// Render a div with text
Inferno.render(<div>Hello world</div>, container);
// Render a div with a boolean attribute
Inferno.render(<div autoFocus='true' />, container);All the following syntaxes are reserved for Inferno's newFragment call
<>
<div>Foo</div>
<div>Bar</div>
</>
<Fragment>
<div>Foo</div>
<div>Bar</div>
</Fragment>This plugin provides few special compile time flags that can be used to optimize an inferno application.
// ChildFlags:
<div $HasTextChildren /> - Children is rendered as pure text
<div $HasVNodeChildren /> - Children is another vNode (Element or Component)
<div $HasNonKeyedChildren /> - Children is always array without keys
<div $HasKeyedChildren /> - Children is array of vNodes having unique keys
<div $ChildFlag={expression} /> - This attribute is used for defining children shape runtime. See inferno-vnode-flags (ChildFlags) for possible values
// Functional flags
<div $Flags={expression} /> - Replaces the vNode flags the plugin computes. See inferno-vnode-flags (VNodeFlags) for possible values$ReCreate has been removed in Inferno 10, and the plugin reports an error for it. To re-create an element, change its
key instead, for example key={version}: Inferno unmounts the old element and mounts a new one when the key changes.
A numeric $ChildFlag is written into the flags like the other child flags. Any other $ChildFlag expression is only
known at runtime, so that element is compiled to the deprecated createVNode (or createFragment), which converts the
value.
$Flags={expression} replaces the flags of the element; the plugin still adds the child bit of the children, and
HasInvalidChildren (16) for a component.
When the JSX shows that the children cannot have the shape a child flag declares, the plugin reports an error that points at the flag, and the build fails. Inferno would otherwise render the children wrong or throw in development. For example:
// An array is not a single vNode
<div $HasVNodeChildren><a/><b/></div>
<div $HasVNodeChildren>{[a, b]}</div>
// Text is not a vNode, and an element is not text
<div $HasVNodeChildren>text</div>
<div $HasTextChildren><a/></div>
// One element is not an array, and keyed children need keys
<ul $HasNonKeyedChildren><li/></ul>
<ul $HasKeyedChildren><li key="1"/><li/></ul>
// Not a ChildFlags value
<div $ChildFlag={3}>{a}</div> x $HasVNodeChildren needs one element or component child, but there are 2 children.
,-[/project/src/App.jsx:1:1]
1 | <div $HasVNodeChildren><a/><b/></div>
: ^^^^^^^^^^^^^^^^^
`----
Dynamic children such as {expression} are not checked, and neither are the children of components. The
uselessFlags option does not change this check.
Child flags are only needed for children whose shape the plugin cannot see, such as {expression} children or a children={expression} prop.
When the children are written as JSX, the plugin sets the child flags itself.
It warns about flags that cannot improve the output:
// The children are known at compile time: the plugin already compiles them with HasVNodeChildren
<div $HasVNodeChildren>
<h1>Hi</h1>
</div>
// Components get their children in props.children, so child flags do nothing
<Foo $HasKeyedChildren>{items}</Foo>
// Only one child flag applies. The order is $ChildFlag, $HasKeyedChildren, $HasNonKeyedChildren,
// $HasTextChildren, $HasVNodeChildren
<div $HasKeyedChildren $HasNonKeyedChildren>{items}</div>
// Fragments have no vNode flags
<Fragment $Flags={1}>{items}</Fragment>The uselessFlags option sets what the plugin does about them:
"warn"(default): print a warning."error": fail the build with an error for each useless flag. For example, CI can use it to keep useless flags out."off": do nothing.
["swc-plugin-inferno", { "uselessFlags": "error" }]swc does not show the warnings of plugins, so the plugin prints them to stderr itself, in the same format as babel-plugin-inferno. A warning shows the file, line and column, the reason and the code around the flag:
swc-plugin-inferno: /project/src/App.jsx:3:10: $HasVNodeChildren is not needed: the children are known at compile time, so the plugin sets their child flags. Child flags only help with dynamic children such as {expression}.
1 | function App() {
2 | return (
> 3 | <div $HasVNodeChildren>
| ^^^^^^^^^^^^^^^^^
4 | <h1>Hi</h1>
5 | </div>
6 | );
With "error" swc reports the same messages as errors, with its own code frames. Any value other than "warn",
"error" or "off" fails with an error, so a typo does not turn the check off silently.
To use a different level in CI, set the plugin options in a JavaScript config, such as the swc-loader options in
webpack.config.js. A folder can get its own level from a separate webpack rule.
{
loader: 'swc-loader',
options: {
jsc: {
parser: { syntax: 'ecmascript', jsx: true },
experimental: {
plugins: [['swc-plugin-inferno', {
// Most CI services set CI=true
uselessFlags: process.env.CI ? 'error' : 'warn'
}]]
}
}
}
}swc-plugin-inferno will automatically import the required methods from inferno library. There is no need to import inferno in every single JSX file. Only import the inferno specific code required by the application.
example:
import {render} from 'inferno'; // only import 'render'
// The plugin will automatically import 'newVNode'
render(<div>1</div>, document.getElementById('root'));A helper that the file already declares at the top level, for example with import { newVNode } from 'inferno', is
used instead of importing it again. In scripts (files without import or export) the helpers are read from
require('inferno').
Set importSource to import the helpers from another module, like the imports option of babel-plugin-inferno:
["swc-plugin-inferno", { "importSource": "inferno-compat" }]You can verify swc-plugin-inferno is used by looking at the compiled output.
This plugin does not generate calls to createElement or h, but instead it uses low level InfernoJS API
newVNode, newComponentVNode, newFragment etc. If you see your JSX being transpiled into createElement calls
it is a good indication that your build configuration is not correct.
