How to setup CesiumJS to use in ReactJS, Webpack, and Typescript

Officially, at this moment, we cannot simply add CesiumJS as a normal module into packages.json and use it directly as other libraries such as lodash. Instead, to use CesiumJS in your React project, there are some configurations needed.

In this post, I will review the current instructions and supplement with my practical experience to make CesiumJS works best in a Typescript-based ReactJS project.

1. Introduction

  1. There is a bridge-library introduced by Hiroki Inoue to lets you use CesiumJS easier, called resium, that you can find the post at
  2. Indeed, using the wrapped-library is the easiest way. However, I prefer to have the full control on Cesium, therefore I went to the next solution, that I found by Google. A post from 2017, a little bit outdated. The post is useful and goes closer to the truth. However, it is not detailed enough and not a clean solution. Anyway, the solution introduced does work.
  3. The best solution I found is officially provided by Cesium at https://cesium.com/docs/tutorials/cesium-and-webpack/

It has a cleaner setup and works well. And to make it easier for the configuration, I would like to add some additional steps in the next section, Setup.

2. Setup

Following the guide step by step at https://cesium.com/docs/tutorials/cesium-and-webpack/

  1. Along with packages added in the guide, I also add the package @types/cesium to use with Typescript (for function recommendation…)
// package.json
{
  "dependencies": {
    ...
    "cesium": "^1.62.0"
  },
  "devDependencies": {
    ...
    "@types/cesium": "^1.59.2",
    "copy-webpack-plugin": "^5.0.4"
  }
}
  1. My project webpack structure is as below
├── config
│   ├── path.js                     // Default path
│   ├── webpack.config.common.js    // Common webpack configurations
│   ├── webpack.config.dev.js       // Inherit from common webpack
│   └── webpack.config.prod.js      // Inherit from common webpack
├── src
├── dist                   // Compiled .js files for deployment
├── node_modules           // installed packages
├── .env                   // dotenv configuration
├── .eslintrc              // Define eslint rules
├── .gitignore             // standard git ignore file
├── package.json           // scripts & packages
├── README.md              
└── yarn.lock

path.js defines the paths to use in webpack config.

// The path to the CesiumJS source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';

However, to make it easier in the config, I explicitly pre-define all paths we need to use for cesium as below

// path.js
'use strict';

const path = require('path');
const process = require('process');

const pathBuilder = (subpath) => path.join(process.cwd(), subpath);

module.exports = {
  ... // Your current defined paths

  // The path to the CesiumJS source code
  cesiumSource: pathBuilder('node_modules/cesium/Source'),
  cesiumSourceAssets: pathBuilder('node_modules/cesium/Source/Assets'),
  cesiumSourceWidgets: pathBuilder('node_modules/cesium/Source/Widgets'),
  cesiumWorkers: pathBuilder('node_modules/cesium/Build/Cesium/Workers'),
};

And the most important part, to add CesiumJS into your webpack.config. Since we will use it in both dev & production, I add the configuration into the webpack.config.common.js file.

// webpack.config.common.js
............... // Your current imports
const CopywebpackPlugin = require('copy-webpack-plugin');
const { DefinePlugin } = require('webpack');

// Common configs
const path = require('./path');

// Everything belongs to cesium is isolated in an object like this
const cesiumConfig = {
  resolve: {
    alias: {
      // CesiumJS module name
      cesiumSource: path.cesiumSource,
    },
  },
  amd: {
    // Enable webpack-friendly use of require in Cesium
    toUrlUndefined: true
  },
  output: {
    // Needed to compile multiline strings in Cesium
    sourcePrefix: '',
  },
  plugins: [
    // Copy Cesium Assets, Widgets, and Workers to a static directory
    new CopywebpackPlugin([ { from: path.cesiumWorkers, to: 'Workers' } ]),
    new CopywebpackPlugin([ { from: path.cesiumSourceAssets, to: 'Assets' } ]),
    new CopywebpackPlugin([ { from: path.cesiumSourceWidgets, to: 'Widgets' } ]),
    new DefinePlugin({
      // Define relative base path in cesium for loading assets
      CESIUM_BASE_URL: JSON.stringify('')
    }),
  ],
};
// Now, using the cesiumConfig in your real configuration
const config = {
  ...cesiumConfig,
  module: {
    unknownContextCritical: false,
    rules: [
      // Make sure you have below rules as default
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      }, {
        test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/,
        use: [ 'url-loader' ]
      }
    ],
  },

  resolve: {
    alias: {
      ...cesiumConfig.resolve.alias,
    },    
  },

  plugins: [
    ...., // your current plugins
    ...cesiumConfig.plugins,
  ],
};

module.exports = config;

That’s it!!! Now your project is ready to use Cesium.

3. Using CesiumJS with Typescript

To make sure you can use CesiumJS with correct UI everywhere, remember to add its CSS at your root component/index

// Import Cesium CSS
// You may need tslint:disable: to disable linting rules errors
import 'cesiumSource/Widgets/widgets.css';

In your component, let's callCesiumPage.tsx, import and use like below

....
// You may need to disable tslint for this require
// Notice that cesiumSource is the alias we defined in 
// webpack.config.common.js resolve.alias 
const cesium: any = require('cesiumSource/Cesium');
// Import @types/cesium to use along with CesiumJS
import { Viewer } from 'cesium';
// Import Cesium CSS if not yet added at root component
import 'cesiumSource/Widgets/widgets.css';

const Root: AnyStyledComponent =
  styled.div({
    width: '100%',
    height: '100%',
  });
const CesiumViewer: AnyStyledComponent = styled.div`
  width: 100%;
  height: 100%;
`;

export interface Props {
  ...
}

/**
 * Project page
 */
class CesiumPage extends Component<Props> {
  private cesiumContainer: RefObject<HTMLDivElement>;

  public constructor(props: Props) {
    super(props);
    this.cesiumContainer = createRef();
  }

  public componentDidMount(): void {
    if (this.cesiumContainer.current) {
      // type Viewer is from @types/cesium
      // whereas, new cesium.Viewer is from the module cesium
      const viewer: Viewer = new cesium.Viewer(this.cesiumContainer.current);
      ........
      console.log('cesium viewer = ', viewer);
    }
  }

  public render(): ReactNode {
    return (
      <Root>
        <CesiumViewer id='cesiumContainer' ref={this.cesiumContainer} />
      </Root>
    );
  }
}
export default CesiumPage;

4. Misc.

In case your project setup with Jest, you may need an additional configuration in jest.config.js like below

moduleNameMapper: {
  // Your current configs...,
  'cesiumSource/Cesium$': '<rootDir>/node_modules/cesium/Source/Cesium.js',
},
transformIgnorePatterns: [
  '/node_modules/(?!(?:ol|lodash-es|cesium)/)[^/]*/.*\\.(js|jsx|ts|tsx)$',
],

DONE!!!

In case you have better solutions or a simpler configuration, please add a comment or share it with our community.

Thank you.

5. References

https://medium.com/@lhviet88/how-to-setup-cesiumjs-to-use-in-reactjs-webpack-and-typescript-a9cd8378f129

Related Posts

How to setup CesiumJS to use in ReactJS, Webpack, and Typescript

Officially, at this moment, we cannot simply add CesiumJS as a normal module into packages.json and use it directly as other libraries such as `lodash.` Instead, to use CesiumJS in your React projec

Read More

Deploying a TypeScript, ExpressJS app into ElasticBeanstalk NodeJS server

When people creating a new NodeJS project, let’s say using ExpressJS, the source code in default is JavaScript. However, what if you prefer TypeScript to keep the source code more consolidated? F

Read More