nexfep
v0.6.1
Published
A desktop application framework based on @webviewjs/webview
Maintainers
Readme
Nexfep
Language: English(now) | 简体中文
A desktop application framework based on @webviewjs/webview
Project Status
🚧 Early Stage
This project is currently in early development, with many core capabilities for desktop application development still missing. The framework is being continuously iterated.
Introduction
Nexfep is a desktop application framework built on @webviewjs/webview, written in TypeScript. It aims to provide developers with a concise and efficient toolchain for building cross-platform desktop applications.
The framework uses a window pool management mechanism, supporting multi-window application scenarios such as code editors, chat tools, dashboards, etc.
Features
- Window Pool Management — Built-in window pool mechanism for automatic reuse and recycling of window resources, avoiding the overhead of frequent creation and destruction
- IPC Communication — Supports bidirectional message communication between the main process and WebView, via injected functions
- Window Control — Provides complete window operation APIs including maximize, minimize, close, title setting, and developer tools
- Drag Regions — Built-in HTML attribute support for defining window drag regions (
nexfep-area-drag, etc.) - System Tray — Create and manage system tray icons with context menus
- Desktop Notifications — Send desktop notifications via
app.utils.notify() - Logging System — Built-in logger with file output and colored console support, with automatic interception of page console messages
- CLI Build Tool — Package your application into a standalone executable via
nexfep build - TypeScript Support — Complete type definitions for excellent development experience
Installation
pnpm add nexfepQuick Start
import { Application } from "nexfep";
const app = new Application();
const window = await app.windows.createWindow({
visible: true,
decoration: false,
});
await window.loadHTML("<h1 nexfep-area-drag>Hello Nexfep!</h1>");Usage Guide
Application
Application is the main entry point of the framework, responsible for managing the application lifecycle and providing access to windows, system tray, and logger.
import { Application } from "nexfep";
const app = new Application();
// or with custom WebView2 user data directory (Windows only)
const app = new Application({ WindowsWebview2UserDataFolder: "C:\\custom\\webview2-data" });
// or with log file path
const app = new Application({ LogFilePath: "./app.log" });Constructor Options
| Option | Type | Default | Description |
| ------------------------------- | ------------------- | ------------------------------------------------ | ------------------------------------------- |
| WindowsWebview2UserDataFolder | string (optional) | %LOCALAPPDATA%\NexfepDevelopment.webview2-data | WebView2 user data directory (Windows only) |
| LogFilePath | string (optional) | none | File path for log output |
Properties
windows— TheWindowPoolinstance for managing browser windowsutils— Utility methods (e.g., desktop notifications)logger— TheLoggerinstance for logging
Methods
createTray(options)— Create a system tray icon with context menucreateLocker(appName)— Create an application instance lock to prevent multiple instancesexit()— Exit the application
Icon
Icon represents an image resource that can be used as a window icon, tray icon, etc.
import { Icon } from "nexfep";
// Create from file path
const icon = Icon.from("./icon.png");
// Create from Buffer or Uint8Array
const icon = Icon.from(buffer);
const icon = Icon.from(uint8Array);
// Create from an object containing data/width/height
const icon = Icon.from({ data: buffer, width: 64, height: 64 });
const icon = Icon.from({ data: uint8Array, width: 64, height: 64 });Static Methods
| Method | Parameters | Return Value | Description |
| ------------------ | ----------------------------------------------------------------------------------------- | ------------ | ------------------------ |
| Icon.from(input) | string | Buffer | Uint8Array | { data: Uint8Array \| Buffer, width?, height? } | Icon | Creates an icon instance |
Logger
The logger supports both file output and colored console output. It can be accessed via app.logger.
app.logger.clear();
app.logger.log("Hello World");
app.logger.error("An error occurred");
app.logger.warn("Warning message");
app.logger.info("Info message");
app.logger.debug("Debug message");Methods
| Method | Description |
| ---------------- | ------------------------------------------------------------------------------ |
| clear() | Clear the log file (only takes effect when the LogFilePath parameter is set) |
| log(message) | Log a message |
| error(message) | Log an error message (red) |
| warn(message) | Log a warning message (yellow) |
| info(message) | Log an info message (blue) |
| debug(message) | Log a debug message (gray) |
Each method accepts either a string or an array of strings.
Page Console Interception
Console calls (console.log, console.error, console.info, console.warn, console.debug) in the page are automatically intercepted and forwarded to the main process logger, with the source window ID included in the output.
Locker
Locker is a class for managing application instance locks, preventing multiple instances from running simultaneously.
const locker = app.createLocker("my-app");
try {
// Try to acquire the lock
await locker.lock();
// You can also pass data to the other instance when acquiring the lock
// await locker.lock(process.argv[2]);
} catch {
// Instance already exists, exit the application
app.exit();
}
// Focus the current instance when other instances acquire the lock
locker.whenLost((data) => {
if (!win.isFocused()) {
win.focus();
}
// If the other instance passed data, you can use it to perform specific actions
// If no data was passed, data will be null
if (data) {
console.log("Other instance passed data:", data);
}
});
// Release the lock
locker.unlock();Methods
lock(data?)— Try to acquire the lockwhenLost(callback)— Register a callback to be called when other instances acquire the lockunlock()— Release the lock
Tray
Create and manage system tray icons with context menus via app.createTray().
import { Icon } from "nexfep";
const icon = Icon.from("./icon.png");
const tray = app.createTray({
id: "my-tray",
tooltip: "My App",
icon: icon, // Icon instance
menuItems: [
{ id: "show", label: "Show Window" },
{ id: "quit", label: "Quit" },
],
});Methods
| Method | Description |
| --------------------- | ------------------------------------------------ |
| addMenuItem(item) | Add a menu item |
| removeMenuItem(id) | Remove a menu item by ID |
| setMenuItems(items) | Replace all menu items |
| setIcon(icon) | Change the tray icon, accepts an Icon instance |
| setTooltip(tooltip) | Change the tooltip text |
| on(event, callback) | Listen for tray events (e.g. 'click') |
| show() | Show the tray icon |
| hide() | Hide the tray icon |
| destroy() | Destroy the tray icon |
tray.on("click", () => {
console.log("Tray clicked");
});
tray.addMenuItem({ id: "about", label: "About" });
tray.setTooltip("Nexfep App");Notifications
Send desktop notifications via app.utils.notify().
const notification = app.utils.notify("Title", { body: "Notification body" });Parameters
title— Notification titleoptions(optional) — Configuration objectbody— Notification body text
Window Pool
WindowPool is the core management class of the framework, responsible for window creation and recycling.
const pool = app.windows;Window Creation
// Use all defaults by omitting parameters
const win = await pool.createWindow();
// Pass partial parameters
const win = await pool.createWindow({
visible: true,
title: "My App",
});
// All parameters
const win = await pool.createWindow({
visible: true, // Whether to show immediately, default true
decoration: true, // Whether to use system decorations, default true
title: "My App", // Window title, default "Nexfep Window"
icon: iconInstance, // Window icon, Icon instance, optional
resizable: true, // Whether the window is resizable, default true
width: 800, // Window width, default 800
height: 600, // Window height, default 600
});Parameters
| Option | Type | Default | Description |
| ------------ | --------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| visible | boolean | true | Whether to immediately show the window |
| decoration | boolean | true | Whether to use system window decorations. When false, the window has no border and requires a custom title bar |
| title | string | "Nexfep Window" | Window title |
| icon | Icon | none | Window icon |
| resizable | boolean | true | Whether the window is resizable |
| width | number | 800 | Window width in pixels |
| height | number | 600 | Window height in pixels |
Window Operations
window.show();
window.hide();
window.maximize();
window.minimize();
window.close();
window.focus();
window.setTitle("New Title");
window.setSize(800, 600);
window.openDevTools();TypeScript Definitions for Frontend API
Add the following content to your frontend's tsconfig.json to use the frontend API definitions:
{
"compilerOptions": {
"types": ["nexfep/frontend"]
}
}Custom Events
Invoke Events
Invoke events via window.invoke in the page:
window.invoke("hello");
window.invoke("hello", "world");Parameters
event— Event namedata(optional) — Any serializable data, will be serialized to JSON string before sending
Listen for Events
Listen for events via pool.handle in the main process:
pool.handle("hello", (data) => {
console.log("Received event hello:", data);
});Parameters
event— Event name, must match the event name when invokingcallback— Event handler function, receives event data asdataparameter. Can return any serializable data, which will be serialized to JSON string and sent back to the frontend as the return value ofwindow.invoke(can also return nothing)
Remove Event Listener
Remove event listener via pool.unhandle in the main process:
pool.unhandle("hello", (data) => {
console.log("Received event hello:", data);
});Parameters
event— Event name, must match the event name when listeningcallback— Event handler function, must match the callback function when listening
Global Variables
Set Variables
Set a global variable via window.setGlobal in the page:
window.setGlobal("hello", "world");Parameters
name— Global variable namevalue— Global variable value, any serializable data, will be serialized to JSON string before sending
Get Variables
Get a global variable via window.getGlobal in the page:
const value = await window.getGlobal("hello");Parameters
name— Global variable name
Global Variable Map
Get a Map<string, any> containing all global variables via pool.global in the main process, which supports operations like set and get:
const globals = pool.global;
globals.set("hello", "world");
const value = globals.get("hello");Inter-Window Communication
The framework supports direct communication between windows via window.broadcast and window.tell, without going through the main process.
Broadcast
Send an event to all other open windows via window.broadcast:
window.broadcast("user-login", { userId: 123 });Parameters
name— Event namedata(optional) — Any serializable data, will be serialized to JSON string before sending
Other windows listen for the broadcast event via window.addEventListener:
window.addEventListener("user-login", (event) => {
console.log("User logged in:", event.detail);
});The main process can send events to all open windows via pool.broadcast, with parameters identical to those of window.broadcast in the page:
pool.broadcast("user-login", { userId: 123 });Tell
Send a message to a specific window by its ID via window.tell:
window.tell(2, "custom-message", { text: "Hello Window 2" });Parameters
to— Target window ID (number)message— Event namedata(optional) — Any serializable data, will be serialized to JSON string before sending
The target window receives the message via window.addEventListener:
window.addEventListener("custom-message", (event) => {
console.log("Received message:", event.detail);
});Each window's ID can be accessed via window.id:
console.log("This window ID:", window.id);The main process can also send messages to this window through win.tell:
win.tell("custom-message", { text: `Hello Window ${win.id}` });Custom Messages
Send Messages
In the page, use window.tell with to 0 to send a message to the main process.
window.tell(0, "custom-message", { hello: "world" });Listen for Messages
Receive messages via onCustomMessage callback in the main process:
pool.onCustomMessage = (window, message, data) => {
console.log(`[${message}] from window ${window.id}:`, data);
};Callback Parameters
window— The window object that sent the messagemessage— Event namedata— Message content, an object type (automatically converted from JSON string viaJSON.parse)
We recommend using window.invoke to communicate between pages and the main process, instead of window.tell.
Window Control Functions
The following injected functions can be directly called in the page for window control:
window.close(); // Close window
window.minimize(); // Minimize window
window.unminimize(); // Restore minimized window
window.toggleMinimize(); // Toggle minimized state
window.maximize(); // Maximize window
window.unmaximize(); // Restore maximized window
window.toggleMaximize(); // Toggle maximized state
window.setTitle("Title"); // Set window title
window.openDevTools(); // Open developer tools
window.closeDevTools(); // Close developer toolsThe following properties are also available in the page:
console.log(window.id); // Window unique identifier
console.log(window.isNexfepLoadDone); // Whether the window has finished loadingDrag Regions
Define window drag regions via HTML attributes without writing additional JavaScript code. These attributes automatically apply -webkit-app-region and app-region CSS properties.
nexfep-area-drag
Makes the entire region and all its child elements draggable. Suitable for scenarios like custom title bars where the entire region needs to be draggable.
<div nexfep-area-drag>
<h1>Title Bar</h1>
<span>Subtitle</span>
</div>nexfep-element-drag
Makes only the specified element itself draggable; child elements are not affected. Suitable for scenarios requiring precise control over the drag region.
<div>
<div nexfep-element-drag>Drag Handle</div>
<p>This part is not draggable</p>
</div>nexfep-no-drag
Makes the specified region and all its child elements non-draggable. Highest priority, can override parent element's drag attributes. Suitable for interactive elements like buttons and input fields.
<div nexfep-area-drag>
<h1>Title Bar</h1>
<button nexfep-no-drag>Click Button</button>
</div>nexfep-auto-drag
Automatically determines drag regions: the entire region is draggable, but common interactive elements (button, input, select, textarea, a) are automatically set to non-draggable. Suitable for complex regions containing multiple interactive elements.
<div nexfep-auto-drag>
<h1>Title Bar</h1>
<button>Automatically non-draggable</button>
<input placeholder="Automatically non-draggable" />
<a href="#">Automatically non-draggable</a>
</div>Load Complete Event
The nexfep-load-done event is triggered after the WebView window finishes loading:
window.addEventListener("nexfep-load-done", () => {
console.log("Nexfep window loaded");
});It can also be checked via the window.isNexfepLoadDone property:
if (window.isNexfepLoadDone) {
// Window is ready
}CLI
help
Use the help command to get a list of available commands:
npx nexfep helpbuild
Nexfep provides a command-line tool for building applications into standalone executables.
Usage
npx nexfep build [options]Options
| Option | Description |
| ------------------------------- | ----------------------------------------------------------- |
| -n, --name <name> | Application name (default: from package.json) |
| -e, --entry <file> | Entry file path (default: from package.json main) |
| -o, --output <dir> | Output directory (default: dist) |
| -i, --ignore <pattern> | Files or directories to ignore (can be used multiple times) |
| -c, --console | Show console window on Windows (default: false) |
| -r, --reinstall | Reinstall production dependencies only before building |
| -s, --skip-clean | Skip cleaning old build files before building |
| -u, --upx <level> | Use UPX to compress the executable, level 0-9 (default: 0) |
| -m, --meta, --metadata <file> | Metadata file path (default: none) |
Examples
# Build using defaults from package.json
nexfep build
# Set custom application name and entry file
nexfep build -n my-app -e ./src/index.js
# Set custom output directory
nexfep build -o ./build
# Ignore multiple patterns
nexfep build -i node_modules -i test -i temp
# Build with UPX compression
nexfep build -u 7This command uses nexfpack to package your application into a standalone executable.
For metadata parameter, you can refer to Nexfpack documentation for details.
Please do not include the outer metadata field, just the internal fields. Like this:
{ "1033": { "FileVersion": "..." } }API
Application
| Method/Property | Parameters | Return Value | Description |
| ----------------------- | -------------------------------------------------- | ------------ | -------------------------------- |
| constructor(options?) | { WindowsWebview2UserDataFolder?, LogFilePath? } | Application | Creates the application instance |
| windows | / | WindowPool | The window pool instance |
| utils | / | __Utils | Utility methods (notifications) |
| logger | / | Logger | The logger instance |
| createTray(options) | see Tray section | Tray | Creates a system tray icon |
| exit() | None | void | Exits the application |
WindowPool
| Method/Property | Parameters | Return Value | Description |
| --------------------------- | ------------------------------------------------------ | ----------------- | ------------------------------------------------------ |
| createWindow(options?) | Optional parameters, see Window Creation section above | Promise<Window> | Creates and returns a window |
| handle(event, callback) | event: string, callback: (data: any) => any | None | Listens for the specified event |
| unhandle(event, callback) | event: string, callback: (data: any) => any | None | Removes the specified event listener |
| global | / | Map<string, any> | A global variable map |
| closeWindow(window) | window: Window | Promise<void> | Closes the specified window and returns it to the pool |
| onCustomMessage | (window: Window, data: string) => void | None | Custom message callback |
Window
| Method/Property | Parameters | Return Value | Description |
| --------------------------------------- | ----------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------- |
| loadURL(url) | url: string — URL to load | Promise<void> | Loads the specified URL |
| loadHTML(html) | html: string — HTML string | Promise<void> | Loads the specified HTML content |
| show() | None | void | Shows the window |
| hide() | None | void | Hides the window |
| maximize() | None | void | Maximizes the window |
| unMaximize() | None | void | Restores the window (cancels maximize) |
| minimize() | None | void | Minimizes the window |
| unMinimize() | None | void | Restores the window (cancels minimize) |
| close() | None | void | Closes the window and returns to pool |
| setTitle(title) | title: string | void | Sets the window title |
| setDecorated(isDecorated) | isDecorated: boolean | void | Sets whether the window has borders and title bar |
| setResizable(resizable) | resizable: boolean | void | Sets whether the window is resizable |
| setLevel(level) | level: -1 | 0 | 1 | void | Sets window level: -1=bottom, 0=normal, 1=top |
| setFullScreen(isFullScreen, options?) | isFullScreen: boolean, options?: { borderless?: boolean } | void | Sets fullscreen mode. borderless: true for borderless fullscreen, otherwise exclusive fullscreen |
| setIcon(icon) | icon: Icon | void | Sets the window icon |
| setSize(width, height) | width: number, height: number | void | Sets the window size in pixels |
| getSize() | None | { width: number, height: number } | Gets the window size in pixels |
| setPosition(x, y) | x: number, y: number | void | Sets the window position in pixels |
| getPosition() | None | { x: number, y: number } | Gets the window position in pixels |
| focus() | None | void | Focuses the window |
| isFocused() | None | boolean | Whether the window has focus |
| isMaximized() | None | boolean | Whether the window is maximized |
| isMinimized() | None | boolean | Whether the window is minimized |
| toggleMaximize() | None | void | Toggles the window maximized state |
| toggleMinimize() | None | void | Toggles the window minimized state |
| openDevTools() | None | void | Opens developer tools |
| closeDevTools() | None | void | Closes developer tools |
| id | None | number | Unique window identifier, auto-incrementing |
Development
pnpm install
pnpm run compileLicense
MIT License
