
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
vKit is a flexible JavaScript/TypeScript library for building dynamic UI declaratively, handling asynchronous calls, managing threads, serializing forms, parsing syntax, and much more.
Example app using the TypeScript module:
import { htmlTag, signal, render } from "vkit-js";
const Br = htmlTag("br");
const Button = htmlTag("button");
function CounterApp() {
const count = signal(0);
return [
Button("Increment", {
onclick: () => count.update(x => x + 1)
}),
Button("Reset counter", {
disabled: () => count() === 0,
onclick: () => count.set(0)
}),
Br(),
"Click count: ", count
];
}
render(CounterApp, document.body);
Before using vKit, you need to have NodeJS installed with npm.
You can add vKit to your existing TypeScript web project:
npm i vkit-jsYou can import the dependencies by name:
import { htmlTag, render, signal } from "vkit-js";
You can also use the vKit CLI if you do not want to use any other build tools and you prefer simple JavaScript:
npm i -g vkit-js to install vKit globallyvkit to create/open a project in the current directoryIf you edit your js and css files inside the generated src directory, you can immediately see the changes in your browser. The $ object will contain vKit's methods.
Use the export command to create a standalone html or js file.
vKit supports universal JavaScript, allowing you to run it seamlessly on a Node.js server. For client-side interactivity, you need to include a script element to load the necessary JavaScript.
You can install the vKit module with this command:
npm i vkit-js
An example server app:
// App.js (universal code)
import { classes, href, param, path } from "vkit-js";
import { A, Body, Li, H1, Head, Main, Nav, Ul } from "./htmlTags.js";
export function App() {
const page = param("page");
return [
H1(classes("red"), "Hello, you are on page ", path()),
Nav(
Ul(
Li(A("Home", href("?"))),
Li(A("About", href("?page=about")))
)
),
Main(
view(() => {
switch (page()) {
case "": return "Home";
case "about": return "About";
default: return "Not Found";
}
})
)
];
}
// server.js (server-only code)
import http from "http";
import { html, renderToStream } from "vkit-js";
import { App } from "./App.js";
import { Body, Head, Html, Script, Style, Title } from "./htmlTags.js";
function Document() {
// The App component can modify the server template, enabling the configuration
// of html, head and body elements through services. Therefore, it is recommended
// to instantiate App before constructing the server template.
const app = App();
return [
html("<!DOCTYPE html>"),
Html(
Head(
Title("Example App"),
Style(".red { color: red; }")
),
Body(
app,
Script({
src: "/bundle.js" // Required for interactivity (use your own URL).
})
)
)
];
}
async function requestListener(req, res) {
// Optionally perform asynchronous operations here, such as fetching data.
res.setHeader("content-type", "text/html; charset=utf-8");
renderToStream(res, Document, {
request: req,
response: res
});
res.end();
}
http.createServer(requestListener).listen(1234);
// bundle.js (client-only code)
import { render } from "vkit-js";
import { App } from "./App.js";
render(App, document.body);
The fundamental building block of a vKit application is a component, which is a function that returns a template. This is a component:
const App = () => "Hello world";
As you can see, even a simple string can be a template. It can be used to render a text node. There are templates available for rendering HTML and SVG elements as well.
import { htmlTag } from "vkit-js";
const H1 = htmlTag("h1");
const App = () => H1("Hello world");
The same HTML tags will be used in many components, so it is worth moving them to a separate file and import them from there.
export const Button = htmlTag("button");
export const Div = htmlTag("div");
export const H1 = htmlTag("h1");
export const Input = htmlTag("input");
export const P = htmlTag("p");
An element's properties can be modified with objects. These objects are templates themselves.
function App() {
return Input({value: "Hello world"});
}
Using the style key, you can set inline CSS properties.
function App() {
return H1("Hello world", {
style: {
color: "red"
}
});
}
The elements can have any number of parameters, each one being a template. They can easily be nested:
function App() {
return Div(
H1("Hello world"),
Input({value: "Hello world"})
);
}
It is also possible to render multiple elements without having to define a container for them. For this, you can simply wrap them in an array.
function App() {
return [
H1("Hello world"),
Input({value: "Hello world"})
];
}
A template serves as a blueprint and describes what should be rendered when render is called. Once a template is rendered, the corresponding DOM nodes are created (or even existing DOM nodes may be reused in a process called hydration).
To render a component in <body> (or any other container element), just call render. This is what a typical application root looks like:
render(App, document.body);
As you have more components, you can build a tree of them:
import { Footer, H1, Header, Main } from "./htmlTags.js";
function Hello(name) {
return H1("Hello ", name);
}
function App() {
return [
Header(Hello("A")),
Main(Hello("B")),
Footer(Hello("C")),
]
}
render(App, document.body);
You can bind a property named on* to attach an event listener to a DOM element.
function ClickableButton() {
return Button("Click me", {
onclick(event) {
console.log("Clicked.", event);
}
});
}
The style method can be used to apply CSS rules to DOM elements easily.
import { style } from "vkit-js";
import { Button } from "./htmlTags.js";
export function SpecialButton(...args) {
return Button(SpecialButtonStyle, args);
}
const SpecialButtonStyle = style({
backgroundColor: [
{value: "#ffffff"},
{on: ":hover", value: "#00ff00"}
],
display: [
{media: "screen and (max-width: 30em)", value: "block"}
],
border: "0",
color: "#000000",
cursor: "pointer"
});
A signal is a container object whose value may change over time. There are two types of signals: writable and computed (read-only).
A writable signal can be created with signal.
const count = signal(42);
Its value can be written with set and read with get.
count.set(50);
console.log(count.get()); // 50
It is also possible to update a value using a function.
count.update(x => x + 12); // equivalent to count.set(count.get() + 12);
count.update((x, y) => x + y, 12); // an extra parameter may be used to pass data to the function
A computed (or read-only) signal can be created with computed. Read-only signals do not have set and update methods.
const myText = computed(() => "Hello world");
You can use the () operator on any signal inside the callback function of computed. It automatically updates the computed signal when an input signal changes.
const name = signal("world");
const myText = computed(() => `Hello ${name()}`);
A computed signal is lazy, which means that its value is not calculated until it is needed somewhere (in the DOM or in a side effect).
const notUsedAnywhere = computed(() => (
"This will never be calculated"
));
A computed signal caches its value, so unless at least one of its inputs change, the value is not recalculated.
const array = signal([]);
const query = signal("");
const filtered = computed(() => (
array().filter((item) => (
item.name.contains(query())
))
));
Computed signals are not immediately updated. The updates are added to a queue instead. To make sure all computed signals are up to date, you can call update.
const a = signal(3);
const b = signal(5);
const aPlusB = computed(() => a() + b());
console.log(aPlusB.get()); // 8
a.set(13);
console.log(aPlusB.get()); // 8
update();
console.log(aPlusB.get()); // 18
A signal can be simply used as a dynamic text in the DOM.
const name = signal("world");
return P("Hello ", name);
It can also be used as a dynamic property of a DOM element.
const name = signal("world");
const color = signal("#ff0000");
return Input({
value: name,
style: {
color
}
});
Attributes can be dynamic too, not just properties.
return Div(
attribute("my-attribute", () => name() + "!")
);
Or classes.
return Div(
classes({
"my-class": () => isMyClassTrue()
})
);
Side effects can also be created that run when the value of an input signal changes.
effect(() => {
console.log(`Hello ${name()}`);
onDestroy(() => {
console.log("Optional cleanup function");
});
});
In many cases, a computed signal only has a single input. In that case, the mapping can be simplified. Just simply call map on the input signal.
const doubleCount = count.map(x => x * 2);
This means the same as:
const doubleCount = computed(() => count() * 2);
Sometimes, modifying existing DOM nodes is not enough. You may want to insert new nodes and remove old ones. A view block is a part of the DOM tree which is destroyed and re-created every time a value changes.
return view(() => show() && Div("This text is shown now!"));
Note that unwanted DOM updates may occur if you use a non-boolean signal as a condition.
return view(() => count() > 3 && Div("Do not do this"));
If you first create a boolean computed signal outside the view block and call it inside, these unwanted DOM updates can be avoided.
const show = computed(() => count() > 3);
return view(() => show() && Div("Do this instead"));
A view list block can be used to render a dynamic list of views (most commonly list items or table rows) efficiently. First, you need a signal that contains an array.
const items = signal([
{
value: "Hello world"
}
]);
Then you can use viewList to create the list items.
return Ul(
viewList(items, (item) => (
Li(item.value)
))
);
In some cases you might need to identify array items by a key (a string or a number) instead of their value. You can do this with the useKey method.
const BooksTable = (books) => html`
<table>
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Year</th>
</tr>
</thead>
<tbody>${
useKey(books, "id").viewList(BookRow)
}</tbody>
</table>'
`;
const BookRow = (bookSignal) => html`
<tr>
<td>${() => bookSignal().title}</td>
<td>${() => bookSignal().author}</td>
<td>${() => bookSignal().year}</td>
</tr>
`;
Components can disappear from the tree when the value of view changes or the corresponding item is no longer in the array used in viewList. When this happens, all side effects caused by creating the component must be reverted. This includes all timeouts, AJAX requests, external state changes initiated by the component.
Fortunately, the onDestroy function can be used here.
function Clock() {
const date = signal(new Date());
const interval = setInterval(() => {
date.set(new Date());
}, 1000);
onDestroy(() => {
clearInterval(interval);
});
return computed(() => date().toLocaleString());
}
You can enqueue a function to be called after the current render cycle using onNextTick. This is useful for interacting with the DOM after it has been rendered (e.g. when playing videos, scrolling, measuring CSS properties of elements, auto-focusing).
function AutoFocus() {
return directive((element) => {
onNextTick(() => element.focus());
});
}
function AutoFocusedInput() {
return Input(AutoFocus());
}
There are two ways a component can get data: from function parameters and from injected services. The difference is that in the latter case, intermediary components do not need to handle data that does not belong to them. By default, service instances are singletons lazily constructed and injected with inject.
function MyComponent() {
const myService = inject(MyService);
return P(myService.getText());
}
const MyService = createInjectable(() => ({
anotherService: inject(AnotherService),
getText() {
return this.anotherService.text;
}
}));
const AnotherService = createInjectable(() => ({
text: "Hello world"
}));
As your application grows, you might need to limit the scope of these services. You can do this easily with provide.
function ProviderComponent() {
return provide([
MyService,
AnotherService
], MyComponent);
}
This means that in the scope of provide you can access the same instance of the service class, but a different instance outside.
Although element (or other) references can be set with simple functions, there is a built-in ref function to create references.
function InputFocusComponent() {
const inputRef = ref();
return html(
'<input>', inputRef,
'<input type="button" value="Focus">', {
onclick: () => inputRef.current.focus()
}
);
}
FAQs
vKit is a web application framework with a command line interface.
We found that vkit-js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.