How it works ?
Puppeteer is based on simple pub/sub pattern implementation.By passing config object and running generateAppEvents Puppeteer creates instance of event bus with default actions responsible for dynamic mounting/unmounting of children applications.
One can also use inititateAppRouter when simple routing is needed. In this case Puppeteer observes and appends hash dynamicaly.
On top of that simple Store functionality is provided which might be usefull for keeping global app state like auth info etc.
Puppeteer uses default loader for dynamic loading of external scripts. It is possible to use custom loader by passing it as 'loader' property in config object. Just make sure it is a funtion that takes file path as argument and returns a promise.
Config
-dev
: true|false(default) flag (logs out events)
- loader
: custom loader function (consumes file string and returns a promise)
- apps
: object describing children apps. App object key is a base for route & action name.- `appName`: this object key will resolve to '#/appname' route and APPNAME prefix for events
- `bundleLocation`: path to external app bundles. List is loaded sequentially.
- `domHook`: document_id where app should be attached
- `mountFuncName`: name of the mountung function exisiting on the window object in children app. Returns unmount `function`.
Installation & Simple Usage
NPMnpm i --save @kamilmac/puppeteer
YARNyarn add @kamilmac/puppeteer
Main app - index.js
import Puppeteer from '@kamilmac/puppeteer'
const config = {
// loader: YOUR_OPTIONAL_LOADER
dev: true,
apps: {
app1: {
bundleLocation: ['app1.js'],
domHook: 'app1',
mountFuncName: 'mountApp1'
},
app2: {
bundleLocation: ['app2.js'],
domHook: 'app2',
mountFuncName: 'mountApp2'
},
},
}
// optional store
let store = {
greeting: 'hello!',
}
const puppeteer = Puppeteer(config)
.generateAppEvents() // generates actions ie. 'APP1:ACTION', 'APP2:ACTION'
.initiateAppRouter() // (optional) attaches router and creates routes for `#/(app1|app2)`
.attachStore(store) // (optional)
// Runs App1 with payload object
// If bundle is not loaded, Puppeteer mounts it dynamically.
// When done, mountFunc is run with (puppeteer, domHook) arguments.
// Look below for mountFunc example
puppeteer.publish('APP1:ACTION', { data })
setTimeout(() => {
// Unmounts App1 and runs App2
puppeteer.publish('APP2:ACTION', { data })
}, 3000)
puppeteer.store('GET', 'greeting').then(value => {
console.log(value) // prints 'hello!'
})
// Write to store
puppeteer.store('SET', { token: 'token' })
// Read from store
puppeteer.store('GET', 'token').then(value => {
console.log(value) // prints 'token'
})
// Subscribes to change in store
puppeteer.store('CHANGE', data => {
console.log('Modified data', data)
})
Children app - app1.js (React example)
// Puppeteer sets puppeteerActive flag to true when initialised.
// Simple check like below allows to run the app in isolation.
if (!window.puppeteerActive) {
ReactDOM.render(
<App/>,
document.getElementById('root')
)
} else {
// Mount function for App1. This is the glue between App1 and Puppeteer
window.mountApp1 = (puppeteer, domHook) => {
ReactDOM.render(
<App puppeteer={puppeteer}/>,
document.getElementById(domHook)
)
return () => {
ReactDOM.unmountComponentAtNode(document.getElementById(domHook))
}
}
}
class App extends React.component {
componentDidMount() {
this.unsub = this.props.puppeteer.subscribe('APP1:ACTION', payload => {
// DO YOUR THING
})
}
componentWillUnmount() {
// unsubscribe when component unmounts
this.unsub()
}
runApp2(payload) {
// Opens App2 and unmounts App1
this.props.puppeteer.publish('APP2:ACTION', payload)
}
render() {
return <h1>APP1</h1>
}
}
Functions
- Puppeteer(config) ⇒
Object
Consumes Config object and creates Puppeteer instance
{ loader: OPTIONALLOADERFUNCTION dev: DEVFLAG apps: {
}, }// Uppercase object key is used as a base for action type and hash name app1: { bundleLocation: 'app1.js', // document element id where app will be attached domHook: 'app1', // Mounting function accesible of window object // should return unmount function mountFuncName: 'mountApp1' }, app2: { bundleLocation: 'app2.js', domHook: 'app2', mountFuncName: 'mountApp2' },
- subscribe(topic, listener) ⇒
function
Subscribes to TOPIC on event buss. Callback is executed with payload data each time 'publish' method is run.
- publish(topic, payload) ⇒
Promise
Publishes event with optional payload.
- store(action, arg) ⇒
Promise
|function
Wrapper for store events
- generateAppEvents() ⇒
Object
Creates default actions/events for children apps in config object. It uses subscribe/publish methods. Route names are based on app keys in config object For each app TOPICs are created:
${APP}:ACTION, ${APP}:MOUNT, ${APP}:UNMOUNT
Apps should communicate by using ${APP}:ACTION topic. MOUNT/UNMOUNT actions are resolved automatically.
- initiateAppRouter() ⇒
Object
Subscribes to ROUTER:CUSTOMHASHCHANGE event which is run each time url location changes. It publishes ROUTER:NONEEXISTINGROUTE event when apps dont match url. It also uses ${APP}:ACTION events for dynamic app mounting. App route names are based on app keys in config object.
- attachStore(Optional) ⇒
Object
Creates simple key-value store which also uses publish/subscribe for communication. Instead of calling publish/subscribe, Puppeteer provides store wrapper for dealing with it.
- getActiveApp() ⇒
String
Returns name of current active app.
Puppeteer(config) ⇒ Object
Consumes Config object and creates Puppeteer instance{ loader: OPTIONALLOADERFUNCTION dev: DEVFLAG apps: {
// Uppercase object key is used as a base for action type and hash name
app1: {
bundleLocation: ['app1.js'],
// document element id where app will be attached
domHook: 'app1',
// Mounting function accesible of window object
// should return unmount function
mountFuncName: 'mountApp1'
},
app2: {
bundleLocation: ['app2.js'],
domHook: 'app2',
mountFuncName: 'mountApp2'
},
},
}Kind: global function
Returns:
Object
- Puppeteer instance| Param | Type | | --- | --- | | config |
Object
| subscribe(topic, listener) ⇒ function
Subscribes to TOPIC on event buss.
Callback is executed with payload data each time 'publish' method is run.Kind: global function
Returns:
function
- Unsubscribe function.| Param | Type | Description | | --- | --- | --- | | topic |
String
| Event to subscribe to. |
| listener | function
| Callback function run when topic event is published. |publish(topic, payload) ⇒ Promise
Publishes event with optional payload.Kind: global function
Returns:
Promise
- Resolves when all subscribed callbacks finished.| Param | Type | Description | | --- | --- | --- | | topic |
String
| Event to publish. |
| payload | Object
| optional payload object. |store(action, arg) ⇒ Promise
\| function
Wrapper for store eventsKind: global function
Returns:
Promise
\| function
- Returns Promise on GET action / Unsubscribe function on CHANGE| Param | Type | Description | | --- | --- | --- | | action |
String
| Event to publish. (GET|SET|CHANGE) |
| arg | String
\| Object
\| function
| Depending on action. (KEYSTRING|OBJECT|CALLBACK) |generateAppEvents() ⇒ Object
Creates default actions/events for children apps in config object.
It uses subscribe/publish methods.
Route names are based on app keys in config object
For each app TOPICs are created:${APP}:ACTION, ${APP}:MOUNT, ${APP}:UNMOUNT
Apps should communicate by using ${APP}:ACTION topic. MOUNT/UNMOUNT actions are resolved automatically.
Kind: global function
Returns:
Object
- returns this instanceinitiateAppRouter() ⇒ Object
Subscribes to ROUTER:CUSTOMHASHCHANGE event
which is run each time url location changes.
It publishes ROUTER:NONEEXISTINGROUTE event when apps dont match url.
It also uses ${APP}:ACTION events for dynamic app mounting.
App route names are based on app keys in config object.Kind: global function
Returns:
Object
- returns this instanceattachStore(Optional) ⇒ Object
Creates simple key-value store which also uses publish/subscribe for communication.
Instead of calling publish/subscribe,
Puppeteer provides store wrapper for dealing with it.Kind: global function
Returns:
Object
- returns this instance.| Param | Type | Description | | --- | --- | --- | | Optional |
Object
| initial store. |getActiveApp() ⇒ String
Returns name of current active app.Kind: global function