Embedding react: rollup configuration
Normally, all my react projects are bootstrapped with create react app. Although it works for most of situations, there's a couple of scenarios where is not enough:
- Exporting the result as a npm component package (topic for another post)
- Create and embeddable application
We can create an embeddable application by exporting the code with rollup and using from outside.
Install #
First install rollup:
npm i --dev rollup
Then install the plugins:
# Import other modules
npm i --dev @rollup/plugin-node-resolve @rollup/plugin-commonjs
# Transpile JS features
npm i --dev rollup-plugin-babel @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
# Postcss support
npm i --dev rollup-plugin-postcss postcss-preset-env
# Development comfort
npm i --dev @rollup/plugin-replace rollup-plugin-filesize rollup-plugin-visualizer rollup-plugin-terser
Config #
A fairly extensive configuration. Remember to replace the output name MyApp
to the name you want to export.
rollup.config.js
:
import replace from "@rollup/plugin-replace";
import resolve from "@rollup/plugin-node-resolve";
import filesize from "rollup-plugin-filesize";
import visualizer from "rollup-plugin-visualizer";
import commonjs from "@rollup/plugin-commonjs";
import babel from "rollup-plugin-babel";
import { terser } from "rollup-plugin-terser";
// Convert CJS modules to ES6, so they can be included in a bundle
import postcss from "rollup-plugin-ptstcss";
import postcssPresetEnv from "postcss-preset-env";
// Use named exports for those libraries
import react from "react";
import reactDom from "react-dom";
const isProd = process.env.NODE_ENV === "production";
const extensions = [".js", ".ts", ".tsx"];
/**
* This configuration is used to generate an self contained embed.js file
*/
export default {
input: "src/embed.tsx",
output: {
file: "../server/public/zendesk-support-widget.js",
format: "umd",
name: "ZendeskSupportWidget"
},
plugins: [
replace({
"process.env.NODE_ENV": JSON.stringify(
isProd ? "production" : "development"
)
}),
resolve({
extensions
}),
commonjs({
include: /node_modules/,
namedExports: {
react: Object.keys(react),
"react-dom": Object.keys(reactDom)
}
}),
postcss({
plugins: [
postcssPresetEnv({
stage: 0
})
]
}),
babel({
extensions,
exclude: /node_modules/,
babelrc: false,
runtimeHelpers: true,
presets: [
[
"@babel/preset-env",
{
useBuiltIns: false
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
plugins: [
// 'react-require',
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-class-properties",
[
"@babel/plugin-proposal-object-rest-spread",
{
useBuiltIns: true
}
]
]
}),
filesize(),
visualizer(),
isProd && terser()
]
};
Scripts #
Just to make it easier:
package.json
:
"scripts": {
"embed": "npm run embed:dev",
"embed:dev": "NODE_ENV=development rollup -c",
"embed:prod": "NODE_ENV=production rollup -c",
"embed:watch": "NODE_ENV=development rollup -c -w",
}
Embeddable #
Instead of exporting the component directly, we use a shim to configure it:
src/embed.tsx
:
import React from 'react'
import ReactDOM from 'react-dom'
import { App } from './App'
interface EmbedOptions {
elementId?: string
endpoint?: string;
}
export default function MyApp(options: EmbedOptions) {
const root = getMountPoint(options.elementId)
ReactDOM.render(<App endpoint={endpoint} />, root)
}
function getMountPoint(id?: string) {
if (id) {
return document.getElementById(id)
}
const root = document.createElement('div')
document.body.appendChild(root)
return root
}
Notice that, although we're exporting a function called MyApp
, the exported actual name is defined by the output.name
at rollup configuration
Usage #
Add the generated script to the html and invoke it:
public/embed.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Embed Test</title>
</head>
<body>
<script src="/embed.js"></script>
<script>
MyApp({
endpoint: "http://myservice.com"
});
</script>
</body>
</html>
Sources:
🖖