LiquidCore
LiquidCore copied to clipboard
A LiquidCore for React Native?
I am installing LiquidCore 0.7.2 in a React Native (0.61.5) app on macOS Catalina, the node installed is 12.16.0 by following instruction here.
The React Native app has a iOS
subdirectory which holds iOS related files. After yarn add liquid core
successfully, the installation is at the last step under /iOS
subdirectories:
pod install
Here is the error created:
pod install
Analyzing dependencies
[!] No podspec found for `liquidcore_bundle` in `.liquidcore/liquidcore_bundle.podspec`
Here is the content of `./liquidcore/liquidcore_bundle.podspec`:
cat liquidcore_bundle.podspec
Pod::Spec.new do |s|
s.name = 'liquidcore_bundle'
s.version = '1.0.0'
s.summary = 'Bundled JS files for LiquidCore'
s.description = 'A pod containing the files generated from the JS bundler'
s.author = ''
s.homepage = 'http'
s.source = { :git => "" }
s.resource_bundles = {
'LiquidCore' => [
'ios_bundle/*.js'
]
}
s.script_phase = { :name => 'Bundle JavaScript Files', :script => '(cd ..; node node_modules/liquidcore/lib/cli.js bundle --platform=ios)' }
Here is app's `package.json`:
{
"name": "ipat_test2",
"version": "0.0.1",
"private": true,
"scripts": {
"server": "node node_modules/liquidcore/lib/cli.js server",
"bundler": "node node_modules/liquidcore/lib/cli.js bundle",
"init": "node node_modules/liquidcore/lib/cli.js init",
"gradle-config": "node node_modules/liquidcore/lib/cli.js gradle",
"pod-config": "node node_modules/liquidcore/lib/cli.js pod",
"postinstall": "node node_modules/liquidcore/lib/cli.js postinstall"
},
"dependencies": {
"liquidcore": "^0.7.2",
"react": "16.9.0",
"react-native": "0.61.5"
},
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/runtime": "^7.6.2",
"@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"eslint": "^6.5.1",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.9.0"
},
"jest": {
"preset": "react-native"
},
"liquidcore": {
"entry": [
"example.js"
],
"pod_options": {
"target": "ipat_test2",
"podfile": "/ios/"
},
"bundler_output": {
"ios": ".liquidcore/ios_bundle"
}
}
}
Any comment is welcome!
Hi @emclab ,
React Native on LiquidCore is not officially supported, but I have successfully hacked it to work. The issues:
- There is little to no documentation, and what documentation I have written is outdated
- It only works with React Native 0.56 at the moment, because it requires hacking deep inside the RN code, and it is hard for me to keep up with the RN release train.
- It is not well tested and there are some known issues
That said, what you can do is use a ReactNativeSurface
addon to LiquidCore, which works with a caraml-core
view.
The documentation of caraml-core
is up-to-date, but unfortunately ReactNativeSurface
is not, even though the code is.
You are welcome to try to get it to work, but it may be difficult without much guidance.
The challenge with RN is getting it to use the same JavaScript context (and in the case of Android, to even use the same JavaScript engine) as LiquidCore. You should be able to easily use them both side-by-side, however they don't talk to each other at all. ReactNativeSurface
is a hacked version of RN that forces them to use the same context/engine. The advantage is that you can seamlessly use both RN and Node.js in the same code. The downside is that it is extremely difficult (for me) to maintain, and unlike Node, where LTS versions are pretty stable for a long time, RN changes constantly and each version is incompatible with the last.
The other option is to write a RN plugin that can spawn a LiquidCore instance. You would still need separate code for the RN pieces and the Node.js pieces, but it would be possible for them to communicate via emitters. This is actually a really good idea and I will look into what it takes to write, but I have no timeframe to give.
As for the error you are getting, that is very curious as obviously the podspec
does exist. Are you updated to the latest cocoapods version? Is there any more output you can share?
Hi, thank you for the comments and ideas. My current installed version of cocoapods is 1.8.4 which is the latest stable release. Before installing liquidCore, I tried nodejs-mobile-react-native and ipfs with no success. I am not sure why those 2 modules are not working together. My mobile project requires both nodejs and ipfs
Here is an post on stackoverflow.com about mobile nodejs modules. I notice on IOS LiquidCore uses JavascriptCore which is used by IOS as well and nodejs-mobile uses chakra engine instead. Also LiquidCore claims complete nodejs runtime environment which my understanding is seamless compatibility with current nodejs modules. Can you comment on LiquidCore vs nodejs-mobile about compatibility (with hundreds of existing nodejs modules) and performance? Also what is the preferring develop platform for the LiquidCore? Native IOS and Android app? React Native is getting more popular but it is not truly native. Many thanks.
Hi @emclab. I have a proposal for how to handle this, which correlates to the second option mentioned above. Doing a deep integration with LiquidCore and RN would be ideal, but it is impossible for me to keep up with the pace of development on RN. As a workaround, I propose building a LiquidCore plugin for React Native. This would effectively decouple the two platforms. The downside is that you would have two separate asynchronous JavaScript codebases only connected through a messaging API.
I have created a proposed API definition, which is attached as a zip file containing the jsdoc
output. Could you take a look at it and see if makes sense, and post any comments/questions?
Classes
- MicroService
A MicroService is the basic building block of LiquidCore. It encapsulates the runtime environment for a client-side micro app. A MicroService is a complete virtual machine whose operation is defined by the code referenced by the service URI. When a MicroService is instantiated, its Node.js environment is set up, its code downloaded (or fetched from cache) from the URI, and is executed in a VM. The host may interact with the VM via a simple message-based API.
Functions
- bundle(bundleName, [options]) ⇒
string
Generates a URL for fetching from the LiquidCore bundle. If app is compiled in DEBUG mode, this will attempt to first download from the development server. If the server is unreachable, then it will default to the packaged bundle. In release mode, this will always reference the packaged bundle.
- serviceFromInstanceId(instanceId) ⇒
MicroService
Each MicroService instance is mapped to a unique string id. This id can be serialized in UIs and the instance retrieved by a call to this method.
- uninstall(serviceURI)
Uninstalls the MicroService from this host, and removes any global data associated with the service.
MicroService
A MicroService is the basic building block of LiquidCore. It encapsulates the runtime environment for a client-side micro app. A MicroService is a complete virtual machine whose operation is defined by the code referenced by the service URI. When a MicroService is instantiated, its Node.js environment is set up, its code downloaded (or fetched from cache) from the URI, and is executed in a VM. The host may interact with the VM via a simple message-based API.
Kind: global class
-
MicroService
- new MicroService(serviceURI)
-
instance
-
.serviceURI :
string
-
.state :
integer
-
.instanceId :
string
-
.addListener(eventName, listener) ⇒
MicroService
-
.emit(eventName, [...args]) ⇒
boolean
-
.eventNames() ⇒
Array.<string>
-
.getMaxListeners() ⇒
integer
- .listenerCount(eventName) ⇒
-
.listeners(eventName) ⇒
Array.<function()>
-
.off(eventName, listener) ⇒
MicroService
-
.on(eventName, listener) ⇒
MicroService
-
.once(eventName, listener) ⇒
MicroService
-
.prependListener(eventName, listener) ⇒
MicroService
-
.prependOnceListener(eventName, listener) ⇒
MicroService
-
.removeAllListeners([eventName]) ⇒
MicroService
-
.removeListener(eventName, listener) ⇒
MicroService
-
.setMaxListeners(n) ⇒
MicroService
-
.start([args]) ⇒
Promise.<MicroService>
-
.exit(exitCode) ⇒
Promise.<MicroService>
- "start"
- "exit" (exitCode)
- "error" (errorMessage)
- "newListener" (eventName, listener)
- "removeListener" (eventName, listener)
-
.serviceURI :
-
static
-
.STATE_INIT :
string
-
.STATE_RUNNING :
string
-
.STATE_DEFUNCT :
string
-
.defaultMaxListeners :
integer
-
.STATE_INIT :
new MicroService(serviceURI)
Creates a new instance of the micro service referenced by serviceURI
.
Param | Type | Description |
---|---|---|
serviceURI | string |
The URI (can be a network URL or local file/resource) of the micro service code |
microService.serviceURI : string
The URI from which the service was started
Kind: instance property of MicroService
Read only: true
microService.state : integer
The current state of the MicroService
Kind: instance property of MicroService
microService.instanceId : string
Each MicroService instance is mapped to a unique string id. This id can be serialized in UIs and the instance retrieved by a call to serviceFromInstanceId.
Kind: instance property of MicroService
Read only: true
microService.addListener(eventName, listener) ⇒ MicroService
Alias for MicroService.on
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The event to listen for |
listener | function |
A callback function |
microService.emit(eventName, [...args]) ⇒ boolean
Synchronously calls each of the listeners registered for the event named eventName, in the order
they were registered, passing the supplied arguments to each.
Note that the arguments will be serialized. That is, functions will be passed as empty objects and
cannot be called.
Returns true if the event had listeners, false otherwise.
Kind: instance method of MicroService
Returns: boolean
- true
if the event had listeners, false
otherwise
Param | Type | Description |
---|---|---|
eventName | string |
The event to emit |
[...args] | any |
Arguments to be passed to listeners |
microService.eventNames() ⇒ Array.<string>
Returns an array listing the events for which the emitter has registered listeners. The values in the array will be strings.
Kind: instance method of MicroService
Returns: Array.<string>
- Array of events for which there are registered emitters
microService.getMaxListeners() ⇒ integer
Returns the current max listener value for the EventEmitter which is either set by MicroService.setMaxListeners or defaults to defaultMaxListeners.
Kind: instance method of MicroService
Returns: integer
- current max listener value
microService.listenerCount(eventName) ⇒
Returns the number of listeners listening to the event named {@param eventName}.
Kind: instance method of MicroService
Returns: the number of listeners listening to the event named eventName.
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event being listened for |
microService.listeners(eventName) ⇒ Array.<function()>
Returns a copy of the array of listeners for the event named {@param eventName}.
Kind: instance method of MicroService
Returns: Array.<function()>
- copy of the array of listeners for the event
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event being listened for |
microService.off(eventName, listener) ⇒ MicroService
Alias for MicroService.removeListener.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event to remove listener from |
listener | function |
A callback function |
microService.on(eventName, listener) ⇒ MicroService
Adds the listener function to the end of the listeners array for the event named eventName. No
checks are made to see if the listener has already been added. Multiple calls passing the same
combination of eventName and listener will result in the listener being added, and called,
multiple times.
Returns a reference to the MicroService, so that calls can be chained.
By default, event listeners are invoked in the order they are added. The MicroService.prependListener method can be used as an alternative to add the event listener to the beginning of the listeners array.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event |
listener | function |
A callback function |
microService.once(eventName, listener) ⇒ MicroService
Adds a one-time listener function for the event named eventName. The next time eventName is
triggered, this listener is removed and then invoked.
Returns a reference to the MicroService, so that calls can be chained.
By default, event listeners are invoked in the order they are added. The MicroService.prependOnceListener method can be used as an alternative to add the event listener to the beginning of the listeners array.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event |
listener | function |
A callback function |
microService.prependListener(eventName, listener) ⇒ MicroService
Adds the listener function to the beginning of the listeners array for the event named eventName.
No checks are made to see if the listener has already been added. Multiple calls passing the same
combination of eventName and listener will result in the listener being added, and called, multiple times.
Returns a reference to the MicroService, so that calls can be chained.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event |
listener | function |
A callback function |
microService.prependOnceListener(eventName, listener) ⇒ MicroService
Adds a one-time listener function for the event named eventName to the beginning of the listeners
array. The next time eventName is triggered, this listener is removed, and then invoked.
Returns a reference to the MicroService, so that calls can be chained.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event |
listener | function |
A callback function |
microService.removeAllListeners([eventName]) ⇒ MicroService
Removes all listeners, or those of the specified eventName.
It is bad practice to remove listeners added elsewhere in the code, particularly when the MicroService
instance was created by some other component or module.
Returns a reference to the MicroService, so that calls can be chained.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
[eventName] | string |
The optional name of the event |
microService.removeListener(eventName, listener) ⇒ MicroService
Removes the specified listener from the listener array for the event named eventName.
removeListener() will remove, at most, one instance of a listener from the listener array. If any single
listener has been added multiple times to the listener array for the specified eventName, then
removeListener() must be called multiple times to remove each instance.
Once an event has been emitted, all listeners attached to it at the time of emitting will be called
in order. This implies that any removeListener() or removeAllListeners() calls after emitting and
before the last listener finishes execution will not remove them from emit() in progress. Subsequent
events will behave as expected.
Because listeners are managed using an internal array, calling this will change the position indices of
any listener registered after the listener being removed. This will not impact the order in which listeners
are called, but it means that any copies of the listener array as returned by the MicroService.listeners
method will need to be recreated.
Returns a reference to the MicroService, so that calls can be chained.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event |
listener | function |
A callback function |
microService.setMaxListeners(n) ⇒ MicroService
By default EventEmitters will print a warning if more than 10 listeners are added for a particular event.
This is a useful default that helps finding memory leaks. Obviously, not all events should be limited to just 10
listeners. setMaxListeners() method allows the limit to be modified for this specific EventEmitter instance. The
value can be set to Infinity (or 0) to indicate an unlimited number of listeners.
Returns a reference to the MicroService, so that calls can be chained.
Kind: instance method of MicroService
Returns: MicroService
- - this object
Param | Type | Description |
---|---|---|
n | integer |
The number of listeners to allow |
microService.start([args]) ⇒ Promise.<MicroService>
Starts the MicroService. This method will return immediately and initialization and
startup will occur asynchronously in a separate thread. It will download the code from
the service URI (if not cached), set the arguments in process.argv
and execute the script.
Kind: instance method of MicroService
Returns: Promise.<MicroService>
- A promise that will be resolved with this object upon successful starting of the process.
Param | Type | Description |
---|---|---|
[args] | any |
The list of arguments to sent to the MicroService. This is similar to running node from a command line. The first two arguments will be the application (node) followed by the local module code (/home/module/[service.js ). args will then be appended in process.argv[2:] |
microService.exit(exitCode) ⇒ Promise.<MicroService>
Instructs the VM to halt execution as quickly as possible
Kind: instance method of MicroService
Returns: Promise.<MicroService>
- A promise that will be resolved upon successful exit
Param | Type | Description |
---|---|---|
exitCode | integer |
The exit code |
"start"
Event indicating when the MicroService has successfully started.
Triggers when the MicroService has been inititialized and the environment is ready to receive event listeners. This is called after the Node.js environment has been set up, but before the micro service JavaScript code has been executed. It is safe to add any event listeners here, but emitted events will not be seen by the JavaScript service until its code has been run. The JavaScript code should emit an event to let the host know that it is ready to receive events.
Kind: event emitted by MicroService
"exit" (exitCode)
Event indicating when the MicroService has successfully exited.
Triggers when the MicroService has exited gracefully. The MicroService is no longer
available and is shutting down. Called immediately before the MicroService exits. This
is a graceful exit, and is mutually exclusive with the 'error' event. Only one of either the exit event
or error event will be triggered from any MicroService.
A single parameter is the exit code emitted by the Node.js process
Kind: event emitted by MicroService
Param | Type | Description |
---|---|---|
exitCode | integer |
The exit code emitted by the Node.js process |
"error" (errorMessage)
Event indicating when the MicroService has exited unexpectedly.
Triggers on any errors that may cause the MicroService to shut down unexpectedly. The
MicroService is no longer available and may have already crashed.
Triggered upon an exception state. This is an unexpected exit, and is mutually exclusive with the
'exit' event. Only one of either the exit event or error event will be triggered from
any MicroService.
A single parameter is the error message of the thrown exception
Kind: event emitted by MicroService
Param | Type | Description |
---|---|---|
errorMessage | string |
The error message of the thrown exception |
"newListener" (eventName, listener)
The MicroService instance will emit its own 'newListener' event before a listener is added
to its internal array of listeners.
Listeners registered for the 'newListener' event will be passed the event name and a reference to
the listener being added.
The fact that the event is triggered before adding the listener has a subtle but important side effect: any additional listeners registered to the same name within the 'newListener' callback will be inserted before the listener that is in the process of being added.
Kind: event emitted by MicroService
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event being listened for |
listener | function |
The event handler function |
"removeListener" (eventName, listener)
The 'removeListener' event is emitted after the listener is removed.
Kind: event emitted by MicroService
Param | Type | Description |
---|---|---|
eventName | string |
The name of the event being listened for |
listener | function |
The event handler function |
MicroService.STATE_INIT : string
State indicating that MicroService is initializing
Kind: static property of MicroService
Read only: true
MicroService.STATE_RUNNING : string
State indicating that MicroService is running
Kind: static property of MicroService
Read only: true
MicroService.STATE_DEFUNCT : string
State indicating that the MicroService is no longer valid (either exited normally or failed)
Kind: static property of MicroService
Read only: true
MicroService.defaultMaxListeners : integer
The default number of max listeners allowed
Kind: static property of MicroService
Read only: true
bundle(bundleName, [options]) ⇒ string
Generates a URL for fetching from the LiquidCore bundle. If app is compiled in DEBUG mode, this will attempt to first download from the development server. If the server is unreachable, then it will default to the packaged bundle. In release mode, this will always reference the packaged bundle.
Kind: global function
Returns: string
- A service URL for use in the MicroService constructor
Param | Type | Description |
---|---|---|
bundleName | string |
The name of the bundled file (ex. 'index' or 'example.js') |
[options] | Object |
Development server options |
[options.port] | integer |
The port on which the server resides |
[options.serverURL] | string |
An alternate server URL |
[options.requestParams] | Object |
An object containing key-value pairs to be added to the GET request |
serviceFromInstanceId(instanceId) ⇒ MicroService
Each MicroService instance is mapped to a unique string id. This id can be serialized in UIs and the instance retrieved by a call to this method.
Kind: global function
Returns: MicroService
- The associated MicroService or undefined if no such service is active.
Param | Type | Description |
---|---|---|
instanceId | string |
An id returned by the instanceId property |
uninstall(serviceURI)
Uninstalls the MicroService from this host, and removes any global data associated with the service.
Kind: global function
Param | Type | Description |
---|---|---|
serviceURI | string |
The URI of the service (should be the same URI that the service was started with). |
I converted to markdown (jsdoc-to-markdown project is great!)
The file level documentation did not get generated. Included here:
React Native LiquidCore API
This is the API for interacting with LiquidCore from React Native. Although both LiquidCore and React Native use JavaScript as their programming language, and npm to distribute, they utlize separate JavaScript context groups on iOS and completely different core JavaScript implementations on Android. Thus, they cannot directly run the same code. This API enables launching a LiquidCore MicroService instance from React Native, and communicating through a messaging wormhole.
Sample integration of the example.js micro service with a simple React Native app:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { MicroService, bundle } from 'liquidcore-rn';
const ExampleBundle = bundle('example.js');
let service = new MicroService(ExampleBundle);
let hello = {
text: 'Loading ...'
};
service.start().then(()=>{
service.on('ready', () => service.emit('ping'))
service.on('pong', payload => { hello.text = payload.message })
})
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>{hello.text}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
});