A state—reactive DOM rendering engine for building UIs. 👻
npm install @sidiousvic/phantom  

#### Phantom lets you build state—reactive UIs using raw HTML strings ejected from functions.
``js
export default function Pizza(slices) {
return
;
}
`#### You update state via actions, and Phantom swaps DOM nodes for you.
`js
phantomStore.fire({ type: "EAT_PIZZA" });
`
$3
$3
$3
$3
$3
💥 Get launched
$3
In Phantom, components are functions that return HTML template literals.
Show code ↯
Template strings allow you to inject dynamic data (including other components) via template literal placeholders
${}.`js
export default function phantomComponent() {
const slices
return ;
}`
leet-html VSCode extension for HTML string highlighting. |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
$3
Use
createPhantomStore to produce your store.
Show code ↯
`js
import { createPhantomStore } from "@sidiousvic/phantom";const data = { slices: ["🍕", "🍕", "🍕"] };
function reducer(state = data, action) {
switch (action.type) {
case "EAT_SLICE": // remove a slice from array
return { ...state, slices: state.slices.slice(0, -1) };
default:
return state;
}
}
const phantomStore = createPhantomStore(reducer);
export default phantomStore;
`$3
Start the Phantom engine by feeding it a component and a store.
Show code ↯
`js
import phantom from "@sidiousvic/phantom";
import phantomComponent from "./phantomComponent.js";
import phantomStore from "./phantomStore.js";const { appear } = phantom(phantomComponent, phantomStore);
appear(); // 3, 2, 1... 💥 initial render!
`Phantom will expose the
appear method. 💥appear will perform the initial DOM render on call, your UI's first _apparition_. 👻
🍕 Data management
Components are cool and all, but what if we want to use dynamic data?
What if we want our UI to _react_ to data changes?
Phantom integrates with a Redux—like store to subscribe the DOM to state updates.
$3
Show code ↯
`js
import phantom from "@sidiousvic/phantom";
import phantomComponent from "./phantomComponent.js";
import phantomStore from "./phantomStore.js";const { appear, data, fire } = phantom(phantomComponent, phantomStore);
appear(); // 3, 2, 1... 🚀 initial render!
`data and fire are pointers to the Phantom store.$3
data returns the current in—store _data_.
Show code ↯
`js
const { slices } = data();
`Data can be passed as arguments to child components.
`js
function phantomComponent() {
const { slices } = data();
return ;
}
`You can use template literal placeholders to inject pieces of state into a component.
`js
export default function Pizza(slices) {
return ;
}
`$3
Show code ↯
An action is an object with a
type key and optional data payload.fire takes an action and dispatches it to the phantomStore, triggering a state change.Phantom will update the DOM on every
fire(action).`js
document.addEventListener("click", eatPizza);function eatPizza(e) {
if (e.target.id === "slices-h1") {
fire({ type: "EAT_PIZZA" }); // DOM will update
}
}
`$3
Show code ↯
Phantom can perform DOM differentiation and swap only the nodes whose state has updated. To activate this behavior,
`js
return ;
`- [x] Include a
data-phantom attribute with the piece(s) of state that you want to subscribe to.
- [x] An id attribute.Phantom will look at at both the
data-phantom and id attributes in order to compute if a DOM node has to be repainted.| ⚠️ If you don't do this, Phantom will repaint the entire DOM on data updates. |
| :----------------------------------------------------------------------------------- |
❓ FAQ
$3
#### A baby panda dies every time you choose a 1MB+\* industrial—level frontend framework to code a pomodoro clock or a personal portfolio page. 🐼
Show rationale ↯
#### You don't drive to the corner store, but walking is overrated. Phantom is the bike you need.
#### 🖍 Declarative
With Phantom, you can write markup in a declarative way ala JSX using raw HTML strings, and inject dynamic data using template literals—staying fully JS native.
No JSX, no complex API, no syntactic hyperglycemia.
#### 🍕 Component—based
Phantom lets you divide your UI into components, abstracting markup into composable functions.
#### 🧪 Reactive
The Phantom engine integrates with a store and subscribes to state updates. It swaps nodes when their data changes.
#### 👩🏾🏭 Closer to the DOM _metal_
Frameworks often abstract too much architecture and functionality out of the DOM. They make you yield too much to _their way_ of doing things—events, effects, styling, routing—you have to find the solutions withing _their_ ecosystem.
Phantom only helps with DOM rendering. It's convenient, but close enough to the DOM that you can integrate it with other solutions without using _fibers_, _combiners_ or _adapters_ of any kind.
\* unpacked size of ReactDOM is 3MB. Vue is 2.98MB. Phantom is < 99 kB.
$3
Show answer ↯
When a component's data changes, Phantom will re—render that node in the DOM by diffing an internal PhantomDOM, a map representation of the DOM.
$3
Show answer ↯
Yes. Phantom uses the internal PhantomExorciser to sanitize HTML strings before injecting them into the DOM.
🔧 Developers
Phantom is written and built using Typescript.
⌨️ Scripts
- [x]
npm run build
generates a static build in dist/ .- [x]
npm run test
runs the tests located in spec.- [x]
npm run example/
runs an example app from examples/$3
👩🏽🔧 Contributing
We are always psyched to welcome contributors to Phantom.
Feel free to raise issues, ask questions or fork the project and play with it.
If you want to submit a contribution, please read our
#### 👷🏽♂️ Contribution Guidelines
Phantom is maintained regularly by @sidiousvic and @nayelyrodarte.
👻 Examples
There are several examples you can run, each furnished with their own
devServer configuration.$3
Click on one of the images above to be taken to an online sandbox.
Devs who have cloned Phantom may use
npm run example/[example name]` and navigate to the url that appears in their terminal.