← Back
config react rollup tools

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:

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:

🖖