Register Merkur widget as custom element
Merkur widget can be registered as custom element. It is helpful for use case where SSR is not important for Merkur widget. We predict that you serve your widget as single JavaScript file with defined assets.
Installation
For easy registration Merkur widget as custom element we created the @merkur/integration-custom-element
module. The module is designed for only client-side.
npm i @merkur/integration-custom-element --save
How to change default Merkur template
The default Merkur template is prepared for SSR so we will remove in below sections useless parts and files to reconfigure default template to only client template. At first create new Merkur widget.
Server part
After created new Merkur widget you change your playground template for creating /server/playground/templates/body.ejs
file to:
<{package.name}></{package-name}> // something like <merkur-widget></merkur-widget>
We changed logic for reviveling widget in playground and added only custom element with name from package.json
to the body part of html. The custom element auto revive Merkur widget. Now you can remove other files in /server/*
folder.
CLI config
You can change merkur.config.mjs
file to add @merkur/integration-custom-element/cli
to extends field.
/**
* @type import('@merkur/cli').defineConfig
*/
export default function () {
return {
extends: ['@merkur/preact/cli' ,'@merkur/integration-custom-element/cli'],
};
}
The @merkur/integration-custom-element/cli
modify default @merkur/cli
configuration (change playground widgetHandler to skip /widget
request, turn off widget server because custom element works only in browser, turn off HMR and use hot reload instead, filter node platform tasks, force generated files to be saved to filesystem as writeToDisk = true, register css bundle plugin for including bundled css file to js).
Widget part
The default Merkur template use config
npm module for resolving application environment. But config
module doesn’t work in browser so we must add support for application environment to our client solution with custom element.
Create new config
folder in /src/
and then there create new file /src/config/index.js
where copy paste code below which add support for two environments development
and production
. The development
environment extends production
environment. So you don’t need copy all options. The webpack tree shaking logic helps removed development
environment in production
build.
import { deepMerge } from '@merkur/integration-custom-element';
import production from './production';
import development from './development';
let environment = null;
if (process.env.NODE_ENV === 'production') {
environment = production;
} else {
environment = deepMerge(production, development);
}
export { environment };
Now you can create your own production
and development
environments in /src/config/production.js
and /src/config/development.js
files. For example /src/config/production.js
file:
export default {
environment: 'production',
cdn: 'http://localhost:4444',
widget: {
apiUrl: 'https://api.github.com/',
},
};
We add our resolved environment to widget props.environment
property in /src/widget.js
. Same as it works in default Merkur template. The custom element don’t support Merkur slots. So we set slotFactories
to empty array. Then you can remove src/components/slots
folder. If you want to inline css bundle to resulted JS file then add import cssBundle from '@merkur/integration-custom-element/cssBundle'
and define inlineStyle
asset with cssBundle as source. At the end register your widget as custom element with registerCustomElement
method which alive widget and connect widget with custom element.
/* eslint-disable no-unused-vars */
import { defineWidget } from '@merkur/core';
import {
componentPlugin,
createViewFactory,
createSlotFactory,
} from '@merkur/plugin-component';
import { errorPlugin } from '@merkur/plugin-error';
import { eventEmitterPlugin } from '@merkur/plugin-event-emitter';
import { registerCustomElement } from '@merkur/integration-custom-element';
import { environment } from './config';
import View from './views/View';
import { name, version } from '../package.json';
import './style.css';
import cssBundle from '@merkur/integration-custom-element/cssBundle';
const widgetDefinition = defineWidget({
name,
version,
viewFactory: createViewFactory((widget) => ({
View,
slotFactories: [],
})),
props: {
environment,
},
$plugins: [componentPlugin, eventEmitterPlugin, errorPlugin],
assets: [
{
name: 'widget.css',
type: 'inlineStyle',
source: cssBundle,
},
],
onClick(widget) {
widget.setState({ counter: widget.state.counter + 1 });
},
onReset(widget) {
widget.setState({ counter: 0 });
},
load(widget) {
// We don't want to set environment into app state
const { environment, ...restProps } = widget.props;
return {
counter: 0,
...restProps,
};
},
});
export default widgetDefinition;
registerCustomElement({ widgetDefinition });