Configuring Cesium to run in Vue.js App

Hi,

I’ve already successfully built a Cesium app using webpack as outlined inthe following Cesium tutorial: This tutorial explains how to integrate the Cesium npm module from webpack, and explores more advanced webpack configurations for optimizing an application using CesiumJS. – Cesium

I’m now trying to create a similar app using Vue.js. Currently I’m unable to get the Vue.js app to run or build due to the following error:

 ERROR  ReferenceError: path is not defined
ReferenceError: path is not defined
    at Object.<anonymous> (C:\Users\virt\Documents\GitHub_Repositories\Websites\Vue_Cesium_Client\vue_cesium_client\vue.config.js:19:11)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at exports.loadModule (C:\Users\virt\Documents\GitHub_Repositories\Websites\Vue_Cesium_Client\vue_cesium_client\node_modules\@vue\cli-shared-utils\lib\module.js:86:14)
    at loadFileConfig (C:\Users\virt\Documents\GitHub_Repositories\Websites\Vue_Cesium_Client\vue_cesium_client\node_modules\@vue\cli-service\lib\util\loadFileConfig.js:30:20)      
    at Service.loadUserOptions (C:\Users\virt\Documents\GitHub_Repositories\Websites\Vue_Cesium_Client\vue_cesium_client\node_modules\@vue\cli-service\lib\Service.js:339:44)        
PS C:\Users\virt\Documents\GitHub_Repositories\Websites\Vue_Cesium_Client\vue_cesium_client>

Steps to recreate:

  1. Install Node.js
  2. Use NPM to insall the VUE CLI:

npm install -g @vue/cli

  1. Create a directory for the project.
  2. Navigate to the project folder.
  3. Create the Vue project:

vue create <project name>

  1. Select option ‘Manually select features’.
  2. Select ‘Babel’ and deselect ‘Linter/Formatter’ by pressing Space Bar and then Return.
  3. Select Vue ‘3.x’.
  4. Select to place Babel ‘In dedicated config files’.
  5. Enter ‘N’ when prompted to save preset for future projects.
  6. Open the project folder in your preferred IDE (e.g. code . for VSCode from the command line).
  7. Navigate to the newly created app folder:

cd <project_name>

  1. Install the Cesium module from npm and add it to package.json:

npm install --save-dev cesium

  1. Test the build command

npm run build

  1. Run the development server:

npm run serve

  1. Add the following to the top of vue.config.js:
const { defineConfig } = require("@vue/cli-service");

// The path to the CesiumJS source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';
  1. Update the module.exports section in vue.config.js as follows:
module.exports = defineConfig({
  transpileDependencies: true,
  context: __dirname,
  entry: {
    app: "./src/main.js",
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  // Add module rules for loading CSS and other files
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/,
        use: ["url-loader"],
      },
    ],
  },
  // Address Webpack compilation issues of Cesium
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),

    // Needed to compile multiline strings in Cesium
    sourcePrefix: "",
  },
  amd: {
    // Enable webpack-friendly use of require in Cesium
    toUrlUndefined: true,
  },
  // Add a Cesium alias that can be referenced in app code
  resolve: {
    alias: {
      // CesiumJS module name
      cesium: path.resolve(__dirname, cesiumSource),
    },
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    // Copy Cesium Assets, Widgets, and Workers to a static directory
    new CopywebpackPlugin({
      patterns: [
        { from: path.join(cesiumSource, cesiumWorkers), to: "Workers" },
        { from: path.join(cesiumSource, "Assets"), to: "Assets" },
        { from: path.join(cesiumSource, "Widgets"), to: "Widgets" },
      ],
    }),
    new webpack.DefinePlugin({
      // Define relative base path in Cesium for loading assets
      CESIUM_BASE_URL: JSON.stringify(""),
    }),
    new Dotenv(),
  ],
});
  1. Rerun the development server:

npm run serve.

My assumption is that the vue.config.js file should accept the webpack configuration but I’m unsure how to implement this properly. Vue provide the following guidance ( Working with Webpack | Vue CLI ) but I’m unclear how that applies in this instance. Any advice appreciated.

Hi @virtualarchitectures,

I was able to get CesiumJS up and running in a Vue app by following these steps:

  1. Install the following dependencies:
npm i cesium # This should be a dependency, not a devDependency
npm i --save-dev copy-webpack-plugin
npm i --save-dev webpack
  1. Configure your vue.config.js file as follows:
const { defineConfig } = require("@vue/cli-service");
const path = require("path"); // Not importing the builtin-path module was causing your build failure.
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { DefinePlugin } = require("webpack");

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

module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    resolve: {
      fallback: { https: false, zlib: false, http: false, url: false },
    },
    plugins: [
      new DefinePlugin({
        CESIUM_BASE_URL: JSON.stringify(""),
      }),
      new CopyWebpackPlugin({
        patterns: [
          { from: path.join(cesiumSource, "Assets"), to: "Assets" },
          { from: path.join(cesiumSource, "ThirdParty"), to: "ThirdParty" },
          { from: path.join(cesiumSource, "Widgets"), to: "Widgets" },
          { from: path.join(cesiumSource, cesiumWorkers), to: "Workers" },
        ],
      }),
    ],
  },
});

  1. Add a new CesiumViewer.vue component:
<script>
import { Viewer } from "cesium";
import "cesium/Source/Widgets/widgets.css";

const viewer = new Viewer("app");
viewer.scene.debugShowFramesPerSecond = true;
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#cesium-container {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}
</style>
  1. Edit your App.vue component to use the CesiumViewer.vue component:
<template>
  <div id="cesium-container">
    <CesiumViewer />
  </div>
</template>

<script>
import CesiumViewer from "./components/CesiumViewer.vue";

export default {
  name: "App",
  components: {
    CesiumViewer,
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

Once you run npm run serve, everything should work:

Hope this helps,
Sam.

Hi @sanjeetsuhag That was super helpful. The steps you outlined get the globe onscreen but I’m still having difficulty recreating my app. I’m trying to pull resources from Cesium ION but I’m having difficulty initializing the viewer (and yes…I have checked my token is valid :wink: ).

<script>
import Cesium from 'cesium/Source/Cesium';
//import { Viewer } from "cesium";
import "cesium/Source/Widgets/widgets.css";

// Reference to access token
Cesium.Ion.defaultAccessToken = '<TOKEN>';

// Initialize the Cesium Viewer in the HTML element with the ID.
const viewer = new Cesium.Viewer("app", {
  timeline: false,
  animation: false,
  baseLayerPicker: false,
  geocoder: false,
});

const buildingsTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());

viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
});

//const viewer = new Viewer("app");

viewer.scene.debugShowFramesPerSecond = true;
</script>

Currently I get the following error:

Uncaught TypeError: Cannot read properties of undefined (reading 'Ion')
    at eval (CesiumViewer.vue?a500:7:1)
    at ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:30:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue?vue&type=script&lang=js:5:215)
    at ./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:118:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue:2:99)
    at ./src/components/CesiumViewer.vue (app.js:96:1)

I think the reference to ION in the error may be a red herring. If I remove the reference to Cesium.Ion.defaultAccessToken the error I get refers to the ‘Viewer’:

app.js:220 Uncaught TypeError: Cannot read properties of undefined (reading 'Viewer')
    at eval (CesiumViewer.vue?a500:10:1)
    at ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:30:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue?vue&type=script&lang=js:5:215)
    at ./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:118:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue:2:99)
    at ./src/components/CesiumViewer.vue (app.js:96:1)

Is the reference import Cesium from 'cesium/Source/Cesium'; I added in the top of the script correct (derived from ES6 style import here: This tutorial explains how to integrate the Cesium npm module from webpack, and explores more advanced webpack configurations for optimizing an application using CesiumJS. – Cesium)? Do I need something additional in my vue.config.js?

Many thanks!

@virtualarchitectures The tutorial you referenced is outdated, unfortunately. You should import modules from the cesium package like this:

import { Ion, Viewer } from "cesium";

...

Sorry @sanjeetsuhag but that doesn’t seem to be working. Is still get the error ‘Cesium is not defined’. VSCode is indicating that the import declarations are unused despite my referencing Cesium.Ion.defaultAccessToken= and const viewer = new Cesium.Viewer().

Changing the above references to Ion.defaultAccessToken= and Viewer() by removing ‘Cesium’ allows the me to serve the app, but doesn’t load the resources from Cesium Ion and still gives the ‘Cesium not defined’ error.

I can successfully import everything using import * as Cesium from "cesium"; instead. This works but I’d rather be importing just what’s needed for my build. I’ve poked about on GitHub but didn’t spot anything relevant. If there is any particular documentation or up-to-date examples you could point me to to help me understand the changes that would be much appreciated. It’s basically the boilerplate stuff that is giving me the headache I’m afraid.

@virtualarchitectures When importing modules directly you don’t need to prefix them with the Cesuim.. For example,

Cesium.Ion.defaultAccessToken = '0';

should instead be:


import { Ion } from "cesium";

Ion.defaultAccessToken = '0';

but doesn’t load the resources from Cesium Ion and still gives the ‘Cesium not defined’ error.

Can you describe what the error is? Are you using the Cesium. anywhere else in your app?


In case it helps, here’s what the source code you posted above should look like:

<script>
import {
  Cartesian3,
  createOsmBuildings,
  Ion,
  Viewer
} from "cesium";
import "cesium/Source/Widgets/widgets.css";

// Reference to access token
Ion.defaultAccessToken = '<TOKEN>';

// Initialize the Cesium Viewer in the HTML element with the ID.
const viewer = new Viewer("app", {
  timeline: false,
  animation: false,
  baseLayerPicker: false,
  geocoder: false,
});

const buildingsTileset = viewer.scene.primitives.add(createOsmBuildings());

viewer.camera.flyTo({
  destination: Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
});

viewer.scene.debugShowFramesPerSecond = true;
</script>

Hi @sanjeetsuhag I realised that I was using the Cesium alias elsewhere where I had been calling Cesium3DTileSet(), IonResource.fromAssetId(), Cartesian3() and Math(). If I remove the preceding references I had to Cesium. and import each namespace or class separately the app serves successfully but still errors in the browser before loading my tileset or moving the camera.

Probably better that I share some of my actual code rather than the boilderplate example to avoid confusion:

<script>
//import * as Cesium from "cesium";
import { Ion, Viewer, IonResource, Cesium3DTileset, Cartesian3, Math } from "cesium";
import "cesium/Source/Widgets/widgets.css";

// Reference to access token
Ion.defaultAccessToken = '<My Token>';

// Initialize the Cesium Viewer in the HTML element with the "app" ID.
const viewer = new Viewer("app", {
  timeline: false,
  animation: false,
  baseLayerPicker: false,
  geocoder: false,
});

//------------------------3DTilesets------------------------//

const tileset = <My Tileset>;

const buildings = viewer.scene.primitives.add(
  new Cesium3DTileset({
    url: IonResource.fromAssetId(tileset),
  })
);

//------------------------Camera------------------------//

tileset.readyPromise.then(function () {
  viewer.camera
    .flyTo({
      destination: Cartesian3.fromDegrees(-0.0970234, 51.3623602, 600),
      orientation: {
        heading: Math.toRadians(0.0),
        pitch: Math.toRadians(-20.0),
      },
    })
    .otherwise(function (error) {
      console.log(error);
    });
});

</script>

The error in the broswer console refers to undefined properties:

app.js:220 Uncaught TypeError: Cannot read properties of undefined (reading 'then')
    at eval (CesiumViewer.vue?a500:29:1)
    at ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:30:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue?vue&type=script&lang=js:5:215)
    at ./src/components/CesiumViewer.vue?vue&type=script&lang=js (app.js:118:1)
    at __webpack_require__ (app.js:217:33)
    at fn (app.js:515:21)
    at eval (CesiumViewer.vue:2:99)
    at ./src/components/CesiumViewer.vue (app.js:96:1)

That refers to webpack. In VSCode the terminal also provides the following warning in case it is relevant:

 warning  in ./src/components/CesiumViewer.vue?vue&type=script&lang=js

export 'default' (reexported as 'default') was not found in '-!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./CesiumViewer.vue?vue&type=script&lang=js' (module has no exports)

My feelings are there’s something wrong with my reference to Ion and/or there’s an issue with my vue.config.js which I assume is sent to webpack. The config file is currently an exact match for the one you’d provided above as is the App.vue so there should be no probs there.

My package.json is as follows in case there are any version issue:

{
  "name": "vue_cesium_client",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "cesium": "^1.99.0",
    "core-js": "^3.8.3",
    "vue": "^3.2.13"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "copy-webpack-plugin": "^11.0.0",
    "dotenv-webpack": "^8.0.1",
    "webpack": "^5.74.0"
  }
}

Apologies for making a meal of it but I’m very confused. If necessary I can go with the import all option. However, it would be really cool to see a Cesium guide or blogpost for Vuejs though.

@virtualarchitectures From the code sample you posted above, it seems like the tileset variable is a number that contains the asset ID for the tileset hosted on Cesium ion. You’re then calling tileset.readyPromise.then which seems to be the source of the error. I believe that should be corrected to buildings.readyPromise.then since the buildings variable is the actual Cesium3DTileset.

Thanks very much. You are quite right. I’m still getting errors but they aren’t preventing me building the app and I believe its on the Vuejs/Webpack side. Many thanks for getting me back up and running on the Cesium side.

1 Like