crossbind
GitHub

Extensions

Almost everything a crossbind app does beyond running your page and your native code is an npm package: the dialogs, the menus, the updater, the installers, the store uploads. Yours are written to the same contract, so you can add to an app, replace a part of it or take a part out. The catalog lists crossbind's own.

What an extension is

A package with three parts: a descriptor the build reads, native code the app's host compiles in, and functions your page imports. Below is a whole one, from examples/app-extension.

There is no plugin loader and no schema to keep in sync. The build reads the descriptor as it loads your config and compiles the sources into the app, with the toolchain that platform's app builds with. A call from the page goes by name, starter.greet, to the function the entry registered, with JSON values and bytes either way.

Code that is the same everywhere goes in app.sources. What one platform needs goes under app.macos, app.ios, app.linux, app.windows or app.android, in Objective-C, Swift, Java or Kotlin where that is what the platform speaks.

crossbind.extension.js
export default function starter() {
return {
name: 'starter',
contract: 1,
root: import.meta.dirname,
app: {
sources: ['native/starter.cpp'],
},
};
}

Add one

crossbind add installs the package with your package manager and lists it in your config. The next build compiles it in.

shell
npx crossbind add <package>
crossbind.config.js
import extension from '<package>/crossbind.extension.js';
 
export default {
target: { runtimeEnv: 'app' },
extensions: [extension()],
};

Replace one

Each default holds roles. Take a role with replaces and the default steps aside for that role alone: menu keeps the menus and the notifications while this one answers tray.set and tray.remove, so the page's code stays as it is. The page-* extensions are roles as well: a browser-like app takes page-navigation and decides where its pages go.

crossbind.extension.js
export default function acmeTray() {
return {
name: 'acme-tray',
contract: 1,
replaces: 'tray',
root: import.meta.dirname,
app: { sources: ['tray.cpp'] },
};
}

Or list an extension under the name of a default other than the page-* ones, which replaces alone takes: it takes that default's place, with a warning. A store or an installer is replaced this way, by an extension named google-play, dmg or another of theirs, with a publisher or an installer of its own.

Leave one out

Its native code, its commands and its stores or installers go with it. A setting nothing would honor stops the build rather than doing nothing: without: ['updater'] beside app.updater is an error. To run only some installers, app.installers names them.

crossbind.config.js
export default {
target: { runtimeEnv: 'app' },
app: { without: ['menu', 'power'] },
};

Write one

The template is the extension above, ready to rename. The playbook is the order of the work, from a name to a publish. Publish it with the npm keyword crossbind-extension, so that others can find it.

shell
npm create crossbind@beta -- my-extension Extension

What it reaches

An extension reaches what the defaults do, through the same calls. extensions.md gives each in full, with the thread it runs on and what its failure does.

WhatThrough
Functions the page calls, with JSON values and bytes, progress messages and cancelsregistry.function(), invoke()
Events for the page, live or held until it asksregistry.events(), events.on(), events.held()
Options, from where the app lists it or from the app's configoptions, fromApp, registry.options()
The app starting and quitting, its windows and pages, going to the backgroundonStart, onQuitRequest, onQuit, onWindow, onClose, onPage, onActive
What opens the app: links, files, continued activities, quick actionsonOpenUrl, onOpenFiles, onUserActivity, onShortcutItem, each with what it declares, as app.urlSchemes
Notifications and push, on iOS and macOSonNotification, onPushToken, onPushMessage
What the page asks for: navigations, sign-ins, downloads, drops, dialogs, permissions, file inputsonNavigation, onHttpAuthentication, onDownload, onDrop, onPageDialog, onPagePermission, onFileInput, each with replaces
Bytes at a URL of the app's own, for the page's <img>, <video> and fetch()registry.route(), routeUrl()
The native window and webview, on the main threadappWindow(), appWebView(), onMainThread()
What one platform's build takes: frameworks, libraries, Info.plist keys, entitlements, Android manifests, Maven packagesframeworks, libraries, plist, entitlements, manifest, maven
Apple's app extensions: a widget, a share extension, a notification serviceapp.ios.targets
The build itself: what installs the app, store uploads, commands under crossbind appinstaller, publisher, commands, each with an api in place of crossbind's own modules

The contract

An extension says which version of the contract it is written for, contract: 1, and a build stops on one it does not take, naming what to update. The contract is extensions.md, <crossbind/extension.h>, <crossbind/app.h> and Android's dev.crossbind.app.Extension and ExtensionHost: anything else of crossbind's is not part of it.

What is new adds to the contract and raises its newest version, and extensions written for the older ones go on building. What breaks one raises the oldest as well. Until crossbind 2.0.0 such a change may come in any release, marked as breaking in its notes; from 2.0.0 it waits for a major version.

Good to know

  • Some enter only where asked. The updater and those that open the app, as links and files, stay out of a build unless the config asks for them.
  • What stays in the host. The windows, the app's folders, quitting, events, calls by name and handing links and files to their owners: crossbind/app/core is what a page gets of it, and every extension builds on it.
  • Compiled in, not loaded. Adding or removing one takes a rebuild, and nothing loads at run time, so what an app ships is what its config listed.
  • Yours to vet. An extension's native code runs in your app's process with your app's rights. crossbind has not reviewed the ones others publish under crossbind-extension: read one as you would any native dependency.
  • Leave one out for what an app exposes, not for size. On a small app, leaving out every default a plain app has took 0.22 MB off 12.82 MB on macOS and 0.23 MB off 5.20 MB on Linux.
  • A TypeScript page reads the defaults' types with /// <reference types="@crossbind/core-app-host/app" />, as it reads vite/client's, because a bundler plugin puts crossbind/app together.
Type to search every guide page and section.
↑↓ navigate↵ openesc close