-
Notifications
You must be signed in to change notification settings - Fork 10
NodeJS Native Part Module System
The wiki page about the Cross Platform Framework explains, how the engine's general architecture is set up. It introduces a universal part and individual native parts, which are mostly unique to every underlying system. The PROCEED project provides a few example implementations of native parts. These include multiple mobile version (iOS and Android) as well as a desktop version written in NodeJS. As we expect the NodeJS version to be deployed the most, it offers an extensive module system for customization of features in the engine.
The NodeJS native part implementation (located at /src/engine/native/node), that is provided by the PROCEED project, exposes an interface, with which developers can easily adapt the native foundation on which the universal part of the engine runs on. All necessary commands (see Cross Platform Framework) that any native part needs to implement, are split up into distinct JS modules which are responsible for small, well-defined subsets of functionality. E.g. the read and write commands, which set and retrieve data for the universal part, are implemented in the @proceed/native-fs module, using the filesystem. The setPort and serve commands for HTTP communication, on the other hand, are implemented in the @proceed/native-express module, using the express library.
All these modules are classes that inherit from the abstract class defined in @proceed/native-module. It describes the signature of any module for the NodeJS native part.
- They need to define a
commandsproperty (e.g. within the constructor), which is an array of strings, containing all implemented commands of the native module (e.g.['read', 'write']). - They also need to implement the method
executeCommand(command, args, send)which is called by the native coordinator code whenever any of the commands is requested to be executed.
The executeCommand(command, args, send) then executes the according functionality using the provided arguments. If the send argument is not used, then the return value of this method can be a promise and will be used to determine the return value for the requested command (which is being sent back to the universal engine part).
In order to use the native modules for the PROCEED engine and also to have an entry point for the execution, all the desired native modules are added as dependencies as well as the general proceed module @proceed/native.
Now, all that needs to be done, is to register all selected native-modules and then start it. For example see src/engine/native/node/index.js:
const native = require('@proceed/native');
const Nfs = require('@proceed/native-fs');
const Nexpress = require('@proceed/native-express');
// ...
native.registerModule(new Nfs());
native.registerModule(new Nexpress());
// ...
native.startEngine({ childProcess: false });
After installing all dependencies (e.g. npm install), the file can be executed to start the PROCEED engine (e.g. node index.js).
By creating an own file with selected native modules, the modules can be swapped for a different native part for the PROCEED engine. For example, the @proceed/native-express module can just be exchanged with the @proceed/native-koa module, to have a different network implementation (KoaJS vs Express) for the same commands. Following the approach from above, the developer can also add his local custom native modules and register them the same way, to provide his own implementation for certain commands.
The same architecture can also be used, to provide custom developer code to the Universal Part, without the need to alter the source code of it directly.
Among others, this is useful if the Universal Part should use JS modules that are only available for a specific platform.
For example, to execute BPMN Scripts safely inside a JS runtime there are the vm2 module and web workers.
The former is available for NodeJS only and the latter for a browser environment.
Since the Universal Part consists of pure JS code, it is useful to inject the vm2 module if the NodeJS Native Part is used.
Any of the native NodeJS modules can additionally provide a onAfterEngineLoaded(engine) method, which gets executed once the universal part loaded (but did not finish initialization) with the engine instance as a parameter. It also has to provide a id property, which enables our webpack bundler to include this module in the built case.
The engine is not only available in form of this repo, but can also be downloaded in ready-to-deploy pre-installed bundles. In order for the fork option of the engine's NodeJS implementation to work, there need to be at least two files: One for the NodeJS native part (with all the modules as introduced above) and one for the universal part.
The fork option determines if the universal part is either started as a child process or if it is imported as a JS module (some process).
With the additional possibility to inject code from the native part into the universal part at runtime (see above), this setup gets a little more complicated. The injected code needs to run in the same JavaScript context as the universal engine part, which means it needs to run in the same process. Thus, the native modules containing those injections need to run in the forked universal part process rather than the native part process like the rest of the native modules. While this is relatively easy to achieve in the unbundled case (just fork a local file, which imports the injection modules), it is not as easy in the bundled case. To achieve this in the bundle as well, we create a third file, which contains the native modules with injection code. For details see here.
This is the Dev Wiki