Prasanna Mahadevaswamy
May 8 · 7 min read

— by Prasanna Mahadevaswamy and supervised by Dirk Kolb.

In this article, we will show you how to build a shared React component library with Cesium and Resium using Webpack.

Rationale

We recently tasked ourselves to build a UI component library that can be used for various solution implementations provided by our Data Fusion Platform.

Building a shared (internal or external) UI component library has its obvious advantages:

  • Re-usability
  • Higher development velocity
  • UI consistency
  • Easier maintenance
  • Standardisation
  • Less costs both for us and for customers

Our idea was to include all the parts needed to quickly build separate and custom (but still consistent) user interfaces for each individual use case of our platform. In the pursuit of re-usability, we decided to include a map component as part of our React component library. After searching for a good map/globe javascript library, we landed on Cesium.

With a quick search, you will find several resources online explaining how to integrate React and Cesium for react apps. But what if you want to include Cesium as part of your own React component library using storybook driven development and keep your consumers unaware of all the steps needed to integrate Cesium with Webpack and React?

At the time of writing this article, we didn’t find a resource online explaining how to include Cesium in a react component library. We thought someone else might be interested in this approach and decided to spread our findings here. Below we have listed some resources we found:

We have also provided a repository that holds an example result of this article.

Used Dependencies

  • Rinse-Repeat: An awesome boilerplate to quickly start a react component library by Connor Wilson.
  • React-Webpack-Boilerplate: A clean React app boilerplate using webpack by Hashem Khalifa.
  • Cesium: The main objective of this article.
  • Resium: Library of react components for cesium by Darwin Education.
  • Webpack: The chosen bundler for the component library.
  • Storybook: An open source tool for developing UI components in isolation.

Below is the full list of the NPM dependencies used in the example:

@babel/core
@babel/plugin-proposal-class-properties
@babel/preset-env
@babel/preset-react
babel-plugin-add-module-exports
babel-loader
style-loader
css-loader
sass-loader
strip-pragma-loader
url-loader
node-sass
eslint-loader
eslint
@storybook/react
copy-webpack-plugin
html-webpack-include-assets-plugin
html-webpack-plugin
webpack
webpack-cli

You will find the exact version information of React, Cesium and other dependencies in the package.json file of the example repository.

Building the Component Library

We will first build the component library. Let’s call it cesium-react-library .

Prepare the Boilerplate

First, let us clone the React component library boilerplate we have chosen.

$ git clone https://github.com/cwlsn/rinse-react cesium-react-library
$ cd cesium-react-library

Then install the already configured dependencies using NPM.

$ cd cesium-react-library
$ npm install

The boilerplate includes storybook with basic configuration. You can run the storybook and test.

npm run storybook

Install Dependencies

Install cesium and resium.

npm install --save cesium resium

Add some babel and webpack plugins.

npm install --save-dev @babel/plugin-proposal-class-properties babel-plugin-add-module-exportsnpm install --save-dev copy-webpack-plugin html-webpack-include-assets-plugin html-webpack-plugin strip-pragma-loader

Configure the Storybook

The boilerplate comes with minimal storybook configuration, but for cesium we need to make some changes. You will find the storybook configuration in the .storybook folder with the files, config.js and addons.js .

Let’s add some more files in the .storybook folder.

  • .babelrc for storybook babel configuration.
  • preview-head.html to include Cesium.
  • webpack.config.js for storybook webpack full control configuration.

Add the following babel presets in .storybook/.babelrc .

{
"presets": [
"@babel/preset-env",
"@babel/react"
]
//...
}

Changes to .storybook/config.js :

The boilerplate storybook configuration picks up all files ending withstories.js for the storybook. Let’s change that to include all files that end in format, .stories.js . To do this, change the regular expression in config.js from /[^/]+\/stories.js$/ to /\.stories\.js$/.

Add the following lines to .storybook/preview-head.html :

<link rel="stylesheet" href="/cesium/Widgets/widgets.css" />
<script src="/cesium/Cesium.js"></script>

Then navigate to .storybook/webpack.config.js , you can refer to the configuration we have added in the example repository for full configuration. Here, we will only mention the configuration that is important for Cesium.

Declare webpack plugins,

const webpack = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");

and add the following plugin configuration.

plugins: [
...config.plugins,
new webpack.DefinePlugin({
CESIUM_BASE_URL: JSON.stringify("/cesium"),
}),
new CopyPlugin([{
from: "node_modules/cesium/Build/Cesium",
to: "cesium",
}])
]

Note: We followed Resium for this configuration.

Configure your React Component Library

At the root of the project, navigate to the file webpack.config.js . This contains the main configuration for our component library (cesium-react-library ).

Add the following path configuration,

const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';

Add plugins,

const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');

Add strip-prama-loader rule,

...
module.exports = {
...
module: {
rules: [
...
{
test: /.js$/,
enforce: "pre",
include: path.resolve(__dirname, cesiumSource),
use: [
{
loader: "strip-pragma-loader",
options: {
pragmas: {
debug: false,
},
},
},
],
}
],
},

Add Cesium alias,

resolve: {
...
alias: {
cesium$: 'cesium/Cesium',
cesium: 'cesium/Source'
}
}

Cesium needs some static files and service workers, we need to copy them to the dist folder. We use the copy-webpack-plugin for this define the base URL of cesium as " .

plugins: [
new CopyWebpackPlugin([
{
from: path.join(cesiumSource, cesiumWorkers),
to: "Workers",
},
{
from: path.join(cesiumSource, "Assets"),
to: "Assets",
},
{
from: path.join(cesiumSource, "Widgets"),
to: "Widgets",
},
]),
new webpack.DefinePlugin({
CESIUM_BASE_URL: JSON.stringify(""),
}),
],

To configure the output of our library,

output: {
path: path.resolve(__dirname, 'dist/'),
publicPath: '',
filename: 'cesium-react-library.js',
libraryTarget: 'umd',
sourcePrefix: ''
},

Other configurations,

amd: {
toUrlUndefined: true
},
node: {
fs: 'empty'
}

Add the preset and plugins for babel located in .babelrc located at the root of the project.

{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"babel-plugin-add-module-exports",
"@babel/plugin-proposal-class-properties"
]
}

Adding the Map Component

Now, let us see create a simple Map component in our library.

Navigate to src/components and create a new folder called map . In this folder, create the following files:

  • Map.js
  • Map.stories.js

Add the following in Map.js, A simple wrapper for the Resium Viewer component.

import React from 'react';
import {Viewer} from 'resium';

class Map extends React.Component {
render() {
return(<Viewer/>);
}
}

export default Map;

Add a story to showcase our map component in the storybook.

import React from 'react';
import { storiesOf } from '@storybook/react';
import Map from './Map';

storiesOf('Map', module).add('basic map', () => (<Map/>));

Exporting the Map Component

To provide flexibility of importing the components from your library, you can follow the export structure as shown below.

The boilerplate already contains the file, src/index.js and src/components/index.js. All components are already exported.

export * from './components';

We need to make our Map component available for the export, we need to create the file, src/components/map/index.js with our map component.

Then, export your map component in src/components/map/index.js .

import Map from './Map';
export default Map;

Navigate to src/components/index.js and add export the Map component again.

export { default as Map } from './map

Testing the Map Component

Run the storybook with npm run storybook . The Map component you created will now be part of the storybook.

Publishing the Component Library

First, build your component library.

cesium-react-library$ npm run build

The result of the build is,

dist/
Assets/
Widgets/
Workers/
cesium-react-library.js

cesium-react-library.js includes Cesium, React and the Map component.

Static resources required by Cesium

Note: You can optimise your build by splitting it to smaller chunks at recommended sizes.

Now you are ready to publish!

npm publish

Consuming your Component Library

We have included an example consumer of the cesium-react-library in the project and it is calledexample-consumer . We have used a Webpack based React app boilerplate to build the example consumer of the library.

Adding the Library as a Dependency

Once your component library is published (you can use verdaccio to do this locally with the --registry argument or configure the registry in package.json ), you can install it in your consumer with,

npm install -save cesium-react-library

Note: You don’t have install cesium and resium again in your consumer application.

Webpack Configuration

Cesium needs some static files and have to be copied to the public folder of the consumer app. There is no need for any other configuration for Cesium. We have again used copy-webpack-plugin for this and html-webpack-include-assets-plugin to add Cesium widgets CSS to the template html.

Below is the Webpack configuration needed.

...
plugins: [
...
new CopyWebpackPlugin([
{
from: 'node_modules/cesium-react-library/dist/Assets',
to: 'Assets',
},
{
from: 'node_modules/cesium-react-library/dist/Widgets',
to: 'Widgets',
},
{
from: 'node_modules/cesium-react-library/dist/Workers',
to: 'Workers',
},
]),
new HtmlWebpackIncludeAssetPlugin({
append: false,
assets: ['Widgets/widgets.css'],
}),
],

Configure for Development

For development purposes we can use npm link to always include the latest changes from the component library in the consumer application.

Navigate to the root of your component library,

cesium-react-library$ npm link

Then go to the root of your consumer React application,

cesium-react-library$ npm link cesium-react-library

Importing the Map Component

To import the Map component you just created,

import { Map } from 'cesium-react-library';

The example consumer has this in App.jsx file, and it looks like this:

...

import { your-map-component } from 'your-component-library-name';
...

return (<your-map-component/>);

Conclusion

We have showcased a simple approach to create a shared component library that includes Cesium and Resium. Although, this method isn’t perfect, Cesium needs the services workers and the other static assets. The result is,

  • A shared component library that includes Cesium and Resium.
  • Simpler and minimal configuration for the consumer apps that use the library.

Traversals Tech Blog

The Traversals Technology Blog

Prasanna Mahadevaswamy

Written by

A full stack software engineer at Traversals Analytics and Intelligence GmbH

Traversals Tech Blog

The Traversals Technology Blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade