➡️ A full-fledged Node.js runtime for Capacitor apps.
Note
This project uses the Node.js for Mobile Apps toolkit to add Node.js support in Android and iOS
Warning
WIP - Work in Progress
Table of contents
- Install
- Examples
- Getting Started
- Complex Projects
- Mobile Node.js APIs differences
- Configuration
- API - Bridge module
- API - Capacitor layer
Capacitor v5 or newer is required. This project isn't compatible with lower versions of Capacitor.
npm install https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.7/capacitor-nodejs.tgz
npx cap sync
- Android
- IOS (coming soon)
- Using the
capacitor-community/electron
plugin:- Windows
- Linux
- macOS
- Web (maybe in future with WebAssembly?)
Example projects can be found in the hampoelz/Capacitor-NodeJS_Examples repository. Each example project is provided in a separate branch.
This guide shows how to add a minimal Node.js project to a Capacitor application and communicate between these processes.
In the example below the Vite build system is used. However, any build system can be used as long as the following criteria are met:
- The Node.js project (to be executed by the engine) must be located in a subdirectory named
nodejs
(or the path set vianodeDir
) of the CapacitorwebDir
. - The Node.js project must have a starting point, this can either be a script named
index.js
or a package.json with amain
field.
For example if the Node.js project needs to be compiled or bundled then this output should be located in the subdirectory of the Capacitor
webDir
.
In this example the directory for the app's source files is named src
, the directory for static assets is named static
,
the directory for the compiled files is named dist
, and the directory for the Node.js project is named nodejs
.
So the configurations should contain at least the following values:
Vite Configurations:
// in vite.config.js or vite.config.ts
{
root: './src',
publicDir: '../static',
build: {
outDir: '../dist'
}
}
Capacitor Configurations:
// in capacitor.config.json or capacitor.config.ts
{
"webDir": 'dist',
"plugins": {
"CapacitorNodeJS": {
"nodeDir": "nodejs"
}
}
}
To meet the criteria from above using Vite, just create a new directory called nodejs
inside the static
directory.
And create a new file called index.js
in it as the starting point.
Vite will copy assets from the
static
directory to the root of thedist
directory as-is. So the creatednodejs
project directory will be placed in the Capacitorwebdir
after build.
The project structure should now look something like this:
capacitor-app/
├── ...
├── dist/ # Capacitor webdir
├── src/ # app source directory
+ ├── static/ # static assets
+ │ ├── nodejs/ # Node.js project directory
+ │ │ ├── index.js # Node.js main script
├── capacitor.config.json
├── vite.config.ts
├── ...
After building and syncing the project, the main script will be executed by the Node.js runtime when the app is launched.
A guide for a more complex Node.js project can be found in the Complex Projects section.
A bridge module to communicate between the Capacitor layer and the Node.js process is built-in.
Use the following code in a Node.js script to wait for messages from the Capacitor layer and send messages back:
const { channel } = require('bridge');
// Listens to "msg-from-capacitor" from the Capacitor layer.
channel.addListener('msg-from-capacitor', message => {
console.log('[Node.js] Message from Capacitor: ' + message);
// Sends a message back to the Capacitor layer.
channel.send('msg-from-nodejs', `Replying to the message '${message}'.`, 'And optionally add more arguments.');
});
Now it is possible to communicate with the Node.js process in the Capacitor app:
import { NodeJS } from 'capacitor-nodejs';
// Listens to "msg-from-nodejs" from the Node.js process.
NodeJS.addListener('msg-from-nodejs', event => {
document.body.innerHTML = `
<p>
<b>Message from Capacitor</b><br>
First argument: ${event.args[0]}<br>
Second argument: ${event.args[1]}
</p>
`;
console.log(event);
});
// Waits for the Node.js process to initialize.
NodeJS.whenReady().then(() => {
// Sends a message to the Node.js process.
NodeJS.send({
eventName: 'msg-from-capacitor',
args: ['Hello from Capacitor!'],
});
});
A full API documentation can be found in the API - Bridge module section.
The examples in this guide are a continuation of the examples in the Getting Started guide.
In the Getting Started guide, the default starting point index.js
was used for the Node.js project.
However, the main script can be renamed or moved to subdirectories for a better organized project.
To change this starting point, add a file called package.json
to the Node.js project, which describes the project more in detail.
Using the main
field in this file, a custom starting point for the Node.js project can be specified.
This should be a module relative to the root of the Node.js project directory.
The package.json file could look like the following, if the main
field is set to server.js
:
// static/nodejs/package.json
{
"name": "capacitor-nodejs-project",
"version": "1.0.0",
"main": "./server.js"
}
The project structure should then change to something like this:
capacitor-app/
├── ...
├── dist/
├── src/
├── static/
│ ├── nodejs/ # Node.js project directory
- │ │ ├── index.js # main script (old)
+ │ │ ├── server.js # main script (new)
+ │ │ ├── package.json # starting point
├── capacitor.config.json
├── vite.config.ts
├── ...
To install Node.js modules, the project requires a package.json
file.
See section Custom starting point for more details.
The modules have to be installed in the Node.js project directory in which the package.json
file was created using the npm CLI.
After installing modules, rebuild and sync the Capacitor project to update the application with the Node.js project.
For convenience, a postinstall script can be added to the main package.json
in the root of the Capacitor project to automatically install the modules of the Node.js project:
// package.json
{
"scripts": {
"postinstall": "cd static/nodejs/ && npm install"
},
// other config options
}
You may also want to add a gitignore file to ignore unnecessary files. To do this, create a new file called
.gitignore
in the Node.js project directory and copy the contents of github/gitignore/Node.gitignore into it.
Important
If the capacitor-community/electron
plugin is used, packaging with the electron-builder may cause problems since it does not include the modules installed in the Node.js project by default.
To fix this issue, add the configuration "includeSubNodeModules": true
to the electron-builder.config.json
.
The Node.js project can quickly grow very large when installing modules. For projects that contain a large number of files, the load time can be reduced by decreasing the number of files and the file sizes.
For this reason, it is recommended to use bunder tools such as Rollup.js. In the following example, Rollup is used to bundle the Node.js project with all its modules to a single file.
To get started install Rollup and its plugins "commonjs", "node-resolve" and "json" into the root of the Capacitor project. If Vite is used as build system, Rollup is already pre-installed and does not need to be installed:
# Install Rollup (If Vite is used, this command is not needed)
npm i --save-dev rollup
# Install Rollup Plugins
npm i --save-dev @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve
Since the Node.js project is now to be bundled, the project structure needs some changes. The Node.js project should no longer be copied directly from Vite to the Capacitor webDir directory, instead it will be bundled with Rollup.
This means that the Node.js project directory needs to be moved from the static assets to somewhere else. For example to the root directory of the Capcitors project:
capacitor-app/
├── ...
├── dist/
├── src/
├── static/
- │ ├── nodejs/
- │ │ ├── node_modules/
- │ │ ├── server.js
- │ │ ├── package.json
- │ │ ├── ...
+ ├── nodejs/
+ │ ├── node_modules/
+ │ ├── server.js
+ │ ├── package.json
+ │ ├── ...
├── capacitor.config.json
├── vite.config.ts
├── ...
Don't forget to update the new path to the project in the postinstall script, if one is used, as described in the Installing Node.js modules section.
After the restructuring of the project, Rollup can be configured.
Create a new file called rollup.config.mjs
with the following content:
// rollup.config.mjs
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import nodeResolve from '@rollup/plugin-node-resolve';
export default {
input: 'nodejs/server.js',
output: {
file: 'dist/nodejs/index.js',
format: 'cjs',
},
external: ['bridge'],
plugins: [
commonjs(),
json(),
nodeResolve({
preferBuiltins: true,
}),
],
};
To add bundling of the Node.js project to the build steps, modify the main package.json
in the root of the Capacitor project
and add && rollup -c rollup.config.mjs
to the build
entry in the scripts
object:
# package.json
{
"scripts": {
- "build": "vite build"
+ "build": "vite build && rollup -c rollup.config.mjs"
}
}
So the project structure should look something like this:
capacitor-app/
├── ...
├── dist/
├── src/
├── nodejs/
│ ├── node_modules/
│ ├── server.js
│ ├── package.json
│ ├── ...
├── capacitor.config.json
+ ├── rollup.config.mjs
├── vite.config.ts
├── ...
After building and syncing the project, the Node.js runtime should start faster now.
By default, the Node.js runtime starts automatically with application start. However, this behavior may not be suitable for all projects.
This behavior can be disabled globally via the startMode
plugin configuration:
# in capacitor.config.json or capacitor.config.ts
{
"webDir": 'dist',
"plugins": {
"CapacitorNodeJS": {
"nodeDir": "nodejs",
+ "startMode": "manual",
},
},
}
Now the Node.js runtime has to be started manually with the NodeJS.start()
command:
import { NodeJS } from 'capacitor-nodejs';
// Starts the Node.js engine.
NodeJS.start();
// Waits for the Node.js process to initialize.
NodeJS.whenReady().then(() => {
// Communicate with the Node.js process.
});
Manually starting the Node.js runtime provides options to override the nodeDir
configuration or even the path for the main script.
In addition, arguments can be passed to the main script and environment variables for the Node.js runtime can be set:
import { NodeJS } from 'capacitor-nodejs';
// Options for starting the Node.js engine manually.
const options = {
args: ['--option', 'value'],
env: {
DB_HOST: 'localhost',
DB_USER: 'myuser',
DB_PASS: 'mypassword',
},
};
// Starts the Node.js engine with properties as set by the `options`.
NodeJS.start(options);
Note
Due to limitations in the Node.js for Mobile Apps toolkit, restarting the runtime after it has finished is not supported.
Mobile platforms are different than the usual desktop platforms in that they require applications to write in specific sandboxed paths and don't have permissions to write elsewhere.
The built-in bridge module provides an API to get a per-user application data directory on each platform:
const { getDataPath } = require('bridge');
// Get a path where data can be read and written.
const dataPath = getDataPath();
Warning
Do not use the Node.js project directory itself for data storage, it will be overwritten after each application update!
To get a path for temporary files, the node.js inbuilt method os.tmpdir()
can be used:
const os = require('os');
// Get a path for temporary files.
const tmpPath = os.tmpdir();
Warning
On Android, the files in the cache are kept until the system needs space, so it increases the application's disk space unless the developer manually deletes them.
Note
This section is based on the documentation of the Node.js for Mobile Apps toolkits.
Not every API is supported on mobile devices. Mobile operating systems do not allow applications to call certain APIs that are expected to be available on other operating systems.
Mobile applications are expected to be a single process.
APIs that create new processes, such as child_process.spawn()
or child_process.fork()
will therefore run into permission issues.
On mobile platforms, the current working directory is the root directory of the file system. This can lead to unexpected behavior in code that assumes that the current working directory is set to the directory of the Node.js project.
On Android creating hard links (fs.link()
and fs.linkSync()
) is not supported.
The internationalization (intl
) module is not available on current nodejs-mobile builds.
os.cpus()
may return inconsistent/unreliable results, since different OS versions will have different permissions for accessing CPU information.os.homedir()
on mobile platforms there is no concept of user home directories.os.platform()
can also return 'android' or 'ios', depending on the platform.
On Android, the files in the cache (os.tmpdir()
) are kept until the system needs space, so it increases the application's disk space unless the developer manually deletes them.
process.cwd()
is the root directory of the file system, instead of the start directory of the project.process.exit()
is not allowed by the Apple App Store guildelines.process.stdin
is not available.process.platform
can also be 'android' or 'ios', depending on the platform.process.versions
includes the 'mobile' key, containing the nodejs-mobile core library version.
The following functions are only available on POSIX platforms, so they are unavailable on Android:
process.getegid()
process.geteuid()
process.getgid()
process.getgroups()
process.getuid()
process.setegid()
process.seteuid()
process.setgid()
process.setgroups()
process.setuid()
These config values are available:
Prop | Type | Description | Default | Since |
---|---|---|---|---|
nodeDir |
string |
Relative path of the integrated Node.js project based on the Capacitor webdir. | "nodejs" |
1.0.0 |
startMode |
'auto' | 'manual' |
Startup mode of the Node.js engine. The following values are accepted: auto : The Node.js engine starts automatically when the application is launched. manual : The Node.js engine is started via the NodeJS.start() method. |
"auto" |
1.0.0 |
In capacitor.config.json
:
{
"plugins": {
"CapacitorNodeJS": {
"nodeDir": "custom-nodejs",
"startMode": "manual"
}
}
}
In capacitor.config.ts
:
/// <reference types="capacitor-nodejs" />
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
plugins: {
CapacitorNodeJS: {
nodeDir: 'custom-nodejs',
startMode: 'manual',
},
},
};
export default config;
The bridge
module is built-in. It provides an API to communicate between the Capacitor layer and the Node.js process, as well as an API to get a per-user application data directory on each platform.
TypeScript declarations for this bridge
module can be manually installed as dev-dependency. If needed, the types-only package can be found under node_modules/capacitor-nodejs/assets/types/bridge.tgz
in the root of the Capacitor project.
onPause: (listener: () => void) => string;
Emits when the application loses focus.
Param | Type |
---|---|
listener |
() => void |
Since: 1.0.0
onResume: (listener: () => void) => string;
Emits when the application gains focus.
Param | Type |
---|---|
listener |
() => void |
Since: 1.0.0
getDataPath: () => string;
Returns a path for a per-user application data directory on each platform, where data can be read and written.
Since: 1.0.0
The channel
class of the bridge
module is an Event Emitter. It provides a few methods to send messages from the Node.js process to the Capacitor layer, and to receive replies from the Capacitor layer.
It has the following method to listen for events and send messages:
send(...)
on(string, ...)
once(string, ...)
addListener(string, ...)
removeListener(...)
removeAllListeners(...)
send: (eventName: string, ...args: any[]) => void
Sends a message to the Capacitor layer via eventName
, along with arguments.
Arguments will be serialized with JSON.
Param | Type | Description | Since |
---|---|---|---|
eventName |
string |
The name of the event being send to. | 1.0.0 |
args |
any[] |
The Array of arguments to send. | 1.0.0 |
Since: 1.0.0
on: (eventName: string, listener: (...args: any[]) => void) => void
Listens to eventName
and calls listener(args...)
when a new message arrives from the Capacitor layer.
Param | Type |
---|---|
eventName |
string |
listener |
(...args: any[]) => void |
listener: (...args: any[]) => void
Param | Type | Description | Since |
---|---|---|---|
args |
any[] |
The received array of arguments. | 1.0.0 |
Since: 1.0.0
once: (eventName: string, listener: (...args: any[]) => void) => void
Listens one time to eventName
and calls listener(args...)
when a new message arrives from the Capacitor layer, after which it is removed.
Param | Type |
---|---|
eventName |
string |
listener |
(...args: any[]) => void |
listener: (...args: any[]) => void
Param | Type | Description | Since |
---|---|---|---|
args |
any[] |
The received array of arguments. | 1.0.0 |
Since: 1.0.0
addListener: (eventName: string, listener: (...args: any[]) => void) => void
Alias for channel.on(string, ...)
.
Param | Type |
---|---|
eventName |
string |
listener |
(...args: any[]) => void |
listener: (...args: any[]) => void
Param | Type | Description | Since |
---|---|---|---|
args |
any[] |
The received array of arguments. | 1.0.0 |
Since: 1.0.0
removeListener: (eventName: string, listener: (...args: any[]) => void) => void
Removes the specified listener
from the listener array for the specified eventName
.
Param | Type |
---|---|
eventName |
string |
listener |
(...args: any[]) => void |
Since: 1.0.0
removeAllListeners: (eventName?: string) => void
Removes all listeners, or those of the specified eventName
.
Param | Type | Description | Since |
---|---|---|---|
eventName |
string |
The name of the event all listeners will be removed from. | 1.0.0 |
Since: 1.0.0
The NodeJS
module is the API used in the Capacitor app. It provides a few methods to send messages from the Node.js layer and wait for them.
It has the following methods:
start(...)
send(...)
whenReady()
addListener(string, ...)
removeListener(...)
removeAllListeners(...)
- Interfaces
- Type Aliases
start(options?: StartOptions) => Promise<void>
Starts the Node.js engine with properties as set by the options
.
Note: This method is only available if the Node.js engine startup mode was set to 'manual'
via the plugin configuration.
Param | Type |
---|---|
options |
StartOptions |
Since: 1.0.0
send(args: ChannelPayloadData) => Promise<void>
Sends a message to the Node.js process.
Param | Type |
---|---|
args |
ChannelPayloadData |
Since: 1.0.0
whenReady() => Promise<void>
Resolves when the Node.js process is initialized.
Since: 1.0.0
addListener(eventName: string, listenerFunc: ChannelListenerCallback) => Promise<PluginListenerHandle> & PluginListenerHandle
Listens to eventName
and calls listenerFunc(data)
when a new message arrives from the Node.js process.
Note: When using the Electron platform, PluginListenerHandle.remove()
does not work due to limitations.
Use removeListener(listenerFunc)
instead.
Param | Type |
---|---|
eventName |
string |
listenerFunc |
ChannelListenerCallback |
Returns: Promise<PluginListenerHandle> & PluginListenerHandle
Since: 1.0.0
removeListener(listenerHandle: PluginListenerHandle) => Promise<void>
Removes the specified listenerHandle
from the listener array for the event it refers to.
Param | Type |
---|---|
listenerHandle |
PluginListenerHandle |
Since: 1.0.0
removeAllListeners(eventName?: string) => Promise<void>
Removes all listeners, or those of the specified eventName
, for this plugin.
Param | Type |
---|---|
eventName |
string |
Since: 1.0.0
An interface containing the options used when starting the Node.js engine manually.
Prop | Type | Description | Since |
---|---|---|---|
nodeDir |
string |
Relative path of the integrated Node.js project based on the Capacitor webdir. Defaults to the nodeDir field of the global plugin configuration. If the nodeDir config is not set, nodejs in the Capacitor webdir is used as Node.js project directory. |
1.0.0 |
script |
string |
The primary entry point to the Node.js program. This should be a module relative to the root of the Node.js project folder. Defaults to the main field in the project's package.json. If the main field is not set, index.js in the project's root folder is used. |
1.0.0 |
args |
string[] |
A list of string arguments. | 1.0.0 |
env |
NodeEnv |
Environment key-value pairs. | 1.0.0 |
An interface that holds environment variables as string key-value pairs.
The payload data to send a message to the web page via eventName
,
along with arguments. Arguments will be serialized with JSON.
Prop | Type | Description | Since |
---|---|---|---|
eventName |
string |
The name of the event being send to. | 1.0.0 |
args |
any[] |
The array of arguments to send. | 1.0.0 |
Prop | Type |
---|---|
remove |
() => Promise<void> |
The callback data object when a message from the Node.js process arrives.
Prop | Type | Description | Since |
---|---|---|---|
args |
any[] |
The received array of arguments. | 1.0.0 |
The callback function to be called when listen to messages from the Node.js process.
(data: ChannelCallbackData): void