Plugin Module Guide
Guide to building Actinium & Reactium plugin modules.
Overview
One of the compelling reasons for using the Reactium Framework is our robust and powerful plugin architecture. There are a two approaches to creating plugins:
Build-time Plugins - extending functionality at build-time
Run-time Plugins - extending functionality at run-time
Build-time Plugins
Build-time Plugins extend the functionality of Reactium or Reactium API (Actinium) projects. When downloaded from the Reactium Plugin Registry, the code from a Build-time Plugin is downloaded into your project reactium_modules (or actinium_modules respectively) directory and bundled with your application at build-time.
Build-time plugins only require a package.json in your plugin directory for publishing it to the Reactium Plugin Registry.
Create A Build-time Plugin
See: Creating A Component for more information on Component Development
Publish A Build-time Plugin
Once you're done building your plugin, publish it to the Reactium Plugin Registry so that it can be installed in other projects.
You will be prompted to create a package.json if it does not exist in the plugin directory.
The plugin files will then be compressed and uploaded to the Reactium Plugin Registry.
Plugins are version controlled and exercise an Access-control List (ACL) to restrict who has permission to install the plugin. By default, plugins are public. Making your plugin private will restrict access to the ACL.
You can update your plugin ACL from your Reactium Plugin Registry account.
Install A Build-time Plugin
Once your plugin has been published, it can be installed in any Actinium or Reactium project.
Note: If you don't have access to the specified plugin an error message will be displayed.
NPM Build-time Plugin
The last type of build-time plugin for Reactium / Actinium are NPM modules. Much like how at build-time Reactium will look for Domain Driven artifacts in your source directory, it will also look for them in your node_modules
directory, albeit with some constraints. NPM build-time modules are constructed much like actinium_modules
and reactium_modules
, however you will likely need to transpile these modules as ES5 common-js modules, using a tool like babel before publishing. This is an advanced topic, and requires understanding of these tools.
Actinium NPM Build-time Plugin Module
An NPM module containing a plugin.js
for Actinium, will be discovered if there is found an NPM module contains an actinium
directory with a file named *plugin.js
. This file will automatically be loaded as an Actinium plugin module. If found directly in an actinium
directory, the following files will be loaded:
actinium/*cloud.js - Parse Cloud functions can be defined here
actinium/*plugin.js - Actinium plugin file. Register your plugin and hooks here.
actinium/*middleware.js - Register express middleware here to be automatically included.
Reactium NPM Build-time Plugin Module
An NPM module containing a directory named reactium-plugin
will be searched for any DDD artifact that would ordinary by globbed by the Node/Express server (such as reactium-boot.js), or any resources that would be loaded automatically by the constructed src/manifest.js manifest file:
domain.js - define a domain namespace
actions.js - exports redux actions for domain
actionTypes.js - exports redux action types for domain
reducers.js - exports redux reducers for a domain
state.js - export default redux initial state for a domain
route.js - export React component route(s)
services.js - export API services for a domain
middleware.js - export redux middleware to be included
enhancer.js - export redux enhancer to be included
zone.js - export rendering zone component registration(s) for a domain
reactium-hooks.js - plugin hooks can be registered here
reactium-boot.js - Node/Express bootstrap hooks can be registered here
Run-time Plugins
Run-time Plugins are served to a Reactium Application from an Actinium instance as pre-compiled static assets via the API, or by adding the CSS/js import to your HTML templates (via modified templates or the Server SDK). This allows you to dynamically add components, register hooks, cause component to render in Zone
components throughout your application, even if those component are not present in the app at build time. This is particularly useful for code-splitting with separate codebases, or facilitating 3rd party extensions to your application at runtime.
Run-time Plugins are similar to Build-time Plugins except their code is not downloaded into your project and bundled with your application. A separate script tag includes the plugin. This allows you to add functionality to your Reactium application with hooks at runtime.
Another difference is that Run-time Plugins can be turned on/off from the Reactium Admin without the application being rebuilt, if they are served from an Actinium plugin.
Creating a Run-time Plugin involves creating both an Actinium Build-time Plugin and a Reactium Plugin
Create A Run-time Plugin
After you've created your Actinium Build-time plugin create your Reactium Run-time Plugin:
Follow the prompt to name your module (the word "plugin" must not appear in the name), and it will generate boiler plate directory under src/app/component/plugin-src
In your src/app/component/plugin-src/<module-name>/index.js
file, you will have access to certain Reactium framework libraries that will be available as global externals, automatically as es module imports. For example, the following import statements do not bundle anything with your runtime module, but sources them externally:
The following modules can be imported without adding any additional weight to your runtime plugins.
axios
classnames
copy-to-clipboard
gsap/umd/TweenMax
moment
object-path
prop-types
react
* (React
is alias fordefault
export)react-router-dom
redux
redux-super-thunk
react-dom
*(ReactDOM
is alias fordefault
export)reactium-core/sdk
* (Reactium
is alias fordefault
export)semver
shallow-equals
underscore
uuid
xss
Important: *
Due to the mechanism used by webpack to proved external dependencies, when external ecmascript-modules above contain BOTHdefault
export AND one or more "named exports", there will be a mandatory alias for the default export.
For example, you must use Reactium
as the default export of reactium-core/sdk
, React
as the default export of react
, etc. You may not import such default exports to a different object name. All named exports can be imported in any way you would ordinarily use them.
Registering Components
One of the most useful features of your runtime plugin is the ability to provide your component or consume external React components from a completely different code bundle (even from a different codebase) at runtime.
In the target application, often the developer will have provided a rendering Zone
in the application, that will render unknown (TBD) components, may have declared a theoretical component of a specific name and property set that is not implemented (i.e. left for you to implement in your plugin), or may have registered a component from the codebase that you may use in your foreign code. These are all powerful mechanisms for providing ways to extend your application without needing to know the exact details of how this will be done.
Sidebar Example
In this example, we'll imagine a Zone
component in the parent application that designates a rendering zone for any component you might choose to render in your third-party runtime plugin:
Above we have a hypothetical SideBar component in your parent application. There are two aspects of this component that can be extended dynamically by a plugin:
A dynamic
Zone
, which can render one or more unknown components provided by your any plugin. Each component rendered by theZone
will receive any properties passed to theZone
component.A hook component, which may or may not exist (will be a null component by default), which may be implemented by some plugin.
In your runtime plugin directory, create a MenuItem.js
file (doesn't matter what you call this). Implement a React component, and register it to the sidebar
zone in your plugin:
Now in your parent application, when the SideBar
component renders, your MenuItem component will dynamically render, even if your plugin is a 3rd party plugin, loaded into the browser.
In addition, you may wish to implement the hook component named SideBarHeader
, create a component in your plugin in a file named SideBarHeader.js
(again this name does not matter). Using the SDK, register this component:
Whenever a component uses useHookComponent
to get the SidebarHeader
component, now your component will render (instead of the null component).
Components registered with Reactium.Component.register()
are replaceable. The last plugin chronologically (and by priority) to register a component will replace whatever implementation was previously being used.
In addition to providing components for the parent application to use, you may use components provided elsewhere in your own plugin if the parent application or another plugin registered any components. This provides a way to share components across codebase without necessarily needed to have them in your codebase at build-time. When you're done developing your plugin eject it to the Actinium Build-time plugin you created above:
You will be prompted to select the plugin and destination directory, and can save this location with a label for subsequent builds.
In your target Actinium plugin location, the css and js assets of your runtime plugin will be copied. For example: If your plugin is called my-runtime-features
, your assets will found in a plugin-assets
directory, containing my-runtime-features.js
and my-runtime-features-plugin.css.
Up until now, you've been developing your runtime plugin, but practically you've been doing this at build-time. To see your plugin actually served at runtime, you need register these assets with the Actinium plugin. Register each asset using the Actinium Plugin SDK:
Now, when this plugin is installed and activated, these assets will be stored and served on the API. The API path URI to these assets will be added to the plugin metadata.
For Reactium Admin, active Actinium plugins that have registered assets of type 'admin' will automatically be served to your Admin at runtime. Additional work may be required in your target app, otherwise. See Serving Runtime Assets below.
Serving Runtime Assets
Now that you have configured your Actinium plugin to serve your runtime plugin assets, you will want your Reactium application to serve those assets. To do so, in some build-time plugin directory in your application (anywhere in your application src or any reactium_module), create a reactium-boot.js
file, and load active plugins from the API, like so:
To test your static assets delivered from the browser through the API, toggle off your local development:
Select your runtime plugin to toggle off local development, and restart your node server.
Toggling local development on/off changes the development: true
to false
in your runtime plugins' reactium-hooks.json
file. This can also be performed manually, or with the CLI.
After restarting the server, you should now see your runtime plugin .js and .css served on the page, and it should function, even if you comment out the index.js file in your plugin locally.
Styles and Assets
Runtime plugins present some unique challenges when it comes to assets you will need for your styles. By default (if you haven't extended your webpack configuration), style loaders and asset loaders are not included in your runtime plugin UMD (Universal Module Definition) build. Reactium's opinion, out-of-the-box, is to utilize SCSS (Sassy CSS) to pre-process styles for your application, and create CSS assets for your app. Even if you were to use webpack to load styles, for production they would often have to be extracted again anyway to avoid FOUC (Flash of unstyled content). This also slows down your Javascript build dramatically over time, and we prefer to process CSS separately.
For build-time plugins, this is not much of a problem, as your styles are usually incorporated into the larger stylesheet at build-time with an import statement.
For runtime plugins, this can mean coming up with some way to use styling for local development, and you will need to understand how that differs from using the runtime plugin css in the wild.
Assets
Reactium uses gulp tasks to copy and optimize assets it finds under any assets
directory, and they often are served at build-time in a flattened /assets/
URI off the document root. This means that for build-time plugin, you can have CSS background images, and predict where they will be served both for local development and in production (e.g. /assets/images/my-background.jpg could be in your CSS as background: url('/assets/images/my-background.jpg')
For runtime plugins, running in production, these assets won't exist (or would exist on a completely different URL, so hard-coding this URL into CSS isn't gonna work), so it would be nice to use them in the stylesheet in a way that will work for both local development and production.
Reactium offers a supplementary DDD asset style-assets.json
, which can allow the runtime-plugin developer to designate certain assets to be embedded in their stylesheet.
Note: style-assets.json can be placed in any directory under src/app
, and will gulp will produce a _reactium-style-variables.scss partial in that same directory. File paths found in the plugin-assets.json must be relative to this json file.
Now that I've created this file, given the existence of the relative files themselves, when I start the local development environment (or run the production build), these assets will be encoded into a SCSS partial in the same directory that can now be used in my runtime plugin's stylesheet. This partial will define an $assets
variable which will be a SCSS map using the property names you specified in your plugin-assets.json, and a data url for the value.
In this way, it is possible to bundle your runtime plugin CSS assets for production.
Where are my styles coming from?
In local development, it can be important to understand how the CSS is being loading into the browser. When your local development is toggled on, (reactium-hooks.json development is set to true), starting the local build will load the locally built css for the runtime plugin into the browser automatically. Changes will be streamed to the browser in real-time using browser sync.
When your local development is toggled off, (reactium-hooks.json development is set to false), styles must be loaded from the production CSS using some other mechanism in your app.
For the @atomic-reactor/admin plugin, any admin plugins registered in Actinium with CSS assets are already setup to be loaded via the API. You can setup a similar mechanism in your own application, see Serving Runtime Assets.
Separate Development / Production Codebase
It is important to know that you should not maintain your runtime plugin src in the same codebase as your deployed application, because its presence in the src/app/component/plugin-src
in your deployable application would essentially negate the whole point of runtime plugins (i.e. code that is introduced to your app only at runtime.) Instead, you'll want to develop your runtime plugin in a separate copy of your base application, so that the compiled runtime assets can be served elsewhere (such as from a CDN or your API plugin). This is because when developing your runtime plugin, during this activity it is essentially a specially structured build-time plugin, but during normal deployed use, these assets are precompiled and loaded in the browser instead.
development codebase - consists of your base application plus one or more runtime plugin source directories under
src/app/components/plugin-src
These constitute the local development environment (uses same facilities as build-time plugins). A special umd.js DDD artifact provides the build entry point for creating the compiled runtime js asset.application codebase - code here loads only the static assets compiled in development codebase, but the source of the runtime plugin cannot be found.
Note that both the reactium-hooks.js file and the umd.js file import from index.js in the boilerplate code. This is so you can make your changes in one place for both local dev and the production asset build, index.js.
Because in the target application codebase, you will only have browser-loaded js and CSS, some artifacts you would ordinary expect to work for a runtime plugin are not available in the runtime context.
A common mistake in creating runtime plugins is forgetting that you can't import components and code from the surrounding application using direct relative or webpack contextual imports (e.g. import Something from 'component/Something';
will work great in local development, but will break in the application codebase.)
Only framework provided externals can be imported into your index.js entry point. See Create a Run-time plugin for list of externals that can be imported. You MAY import from NPM dev dependency libraries that are not listed, but note that these dependencies will be bundled with your runtime library, sometimes considerably adding to the duplicate weight of the plugin. Consider "hosting" these components using the parent application (e.g. Reactium.Component.register()
to make these available to all the runtime plugins vis useHookComponent()
)
Treat your plugin src directory as something that should be encapsulated or interacting only with the SDK, not as part of the larger application codebase, or you risk it not building correctly for runtime usage. Build-time DDD artifacts like route.js, reactium-hooks.js, reactium-boot.js, state.js, actions.js, reducers.js, etc. will work in the local development environment, but will not be included in your UMD, and will not apply in the application codebase. Hooks registrations, SDK extensions, component registrations, should be added to the index.js entry point, and reactium-hooks.js should not be modified in your local development codebase, as it will create confusing differences between dev and production.
Publish A Run-time Plugin
Once your Reactium Run-time Plugin has been ejected into your Actinium Build-time Plugin you can publish the Actinium Build-time Plugin to the Reactium Plugin Registry:
Install A Run-time Plugin
Once the Actinium Build-time Plugin serving the Reactium Run-time Plugin has been published, you can install it to any Actinium project which will make it available in your Reactium project:
Last updated