Skip to main content

Getting Started with Vite

Setup

To begin, let's initiate a new Vite + TypeScript project from scratch:

Prefer React?

If you'd rather use React.js, check out this guide instead.

# npm 6.x
npm create vite@latest my-novorender-app --template vanilla-ts

# npm 7+, extra double-dash is needed:
npm create vite@latest my-novorender-app -- --template vanilla-ts

Once the above command has run successfully, you should notice a newly created directory named my-novorender-app. Navigate into this directory, and then proceed with the following commands to complete the project creation process:

npm install

Open the project in your favorite IDE and you should see a directory structure something like this:

📦 my-novorender-app
┣ 📂public
┃ ┗ 📜vite.svg ⛔
┣ 📂src
┃ ┣ 📜counter.ts ⛔
┃ ┣ 📜main.ts ⛔
┃ ┣ 📜style.css ⛔
┃ ┣ 📜typescript.svg ⛔
┃ ┗ 📜vite-env.d.ts
┣ 📜.gitignore
┣ 📜index.html
┣ 📜package.json
┗ 📜tsconfig.json

Let's clean up this project by removing some redundant files, so we don't have any unnecessary boilerplate code left, delete the files marked with ⛔ symbol. Afterward, create an empty file named main.ts within the src directory.

Installing Web API

Run the following command to install the Novorender Web API package:

npm i --save @novorender/api

The NPM package contains pre-built ESM bundles for the main script and worker scripts as well as the binary resource dependencies. We removed support for legacy UMD modules.

info

A copy of all the original typescript source code along with sourcemaps is included too. We consider the source code an important part of our documentation.

Besides installing the package, you must make sure the files in the public/ directory of this package are available to your server, this is covered in next section.

Copying required resources

Before we move forward to actually implementing the Novorender Web API, we need to find some way to copy the resources from node_modules/@novorender/api/public to Vite's /public dir, one way of doing this is to create a node script and use fs module to copy the files. However, there are alternative methods, such as using vite-plugin-static-copy or CopyWebpackPlugin if you are using Webpack. The choice of implementation ultimately depends on your preference, but for the sake of flexibility across different bundlers, we will proceed with the Node script approach.

Create a file named copy.js and insert the following JavaScript code within it:

copy.js
import { promises as fs } from "fs";

const sourceDir = "node_modules/@novorender/api/public";
const destDir = "public/novorender/api";

await fs.cp(sourceDir, destDir, { recursive: true });
info

The above script utilizes some newer APIs, so please make sure your nodejs version is >= 16.

To execute this script, use the node copy.js command. Assuming that the execution was successful, you should now see all the copied resources in /public/novorender/api/ directory.

tip

You can automate the process of copying the required resources by adding this command to npm's postinstall hook. This way, the necessary resources will be copied automatically whenever project dependencies are installed with npm install.

Server requirements

Our API uses advanced, cutting edge javascript APIs, many of which comes with certain security requirements. In general, the following two global properties have to be true: isSecureContext and crossOriginIsolated.

To make it all work, your server has to ensure:

  1. A secure context. In practice, this means HTTP on localhost (for debugging only) and HTTPS everywhere else, including LAN.

  2. Cross origin HTTP headers for top level documents.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

This can be done by adding the following headers in Vite config file. Create a new file named vite.config.ts in the project's root directory and paste the following code into it:

vite.config.ts
export default {
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
}
}
}
Adding headers via ModHeader

If the above method of adding headers doesn't work or you don't have access to the build configuration, you can utilize the ModHeader browser extension. This extension allows you to modify HTTP request and response headers, providing an alternative way to address header-related issues.

ModHeader usage demonstration gif

info

It's important to note that the method described above for enabling Cross-Origin headers is primarily intended for development mode. When transitioning to a production environment, you will need to configure these headers on your server. The specific implementation will depend on the server you are using, so please consult the relevant documentation for detailed instructions.

  1. MIME type text/javascript for javascript files and application/wasm for web assembly files.

  2. Any resources loaded from a separate domain has be configured with CORS to allow your domain.

  3. Service workers script at the appropriate location, preferably at the root of your domain. See MDN for more.

Typescript

Using our APIs from javascript is possible but strongly discouraged. We rely heavily on typescript to help users catch common errors at edit/compile time. Technical support will only be provided for typescript users.

This package is built using version ^5.6.2 of typescript. As a rule of thumb, you should upgrade to the latest version of typescript whenever a new version is released.

If you plan to do your own bundling and use our sources directly, you may want to use our tsconfig.json as a baseline for your own:

{
"extends": "node_modules/@novorender/api/tsconfig.json", // or wherever...
"compilerOptions": {
...
}
}

We generally use ESNext as target since we only support latest version of browsers with cutting edge support for 3D rendering. Also, we use relatively new typescript features such as verbatimModuleSyntax and allowArbitraryExtensions.

Writing some code

Now that we have a proper project in place, we can go ahead implementing the actual API.

Providing required resources

Before we create a view, we need to provide it with some resources;

  • Canvas
  • Device profile
  • Imports

These resources are crucial for setting up and configuring the view properly.

Canvas

To begin, let's create an HTML canvas element:

index.html
<canvas id="canvas" style="width:100%; height:100%"></canvas>
info

It's crucial to set the CSS width and height properties on the canvas element to prevent runaway resize feedback loops. This ensures that the canvas has the correct dimensions and behaves as expected.

Now, open the src/main.ts file and insert the following code to obtain a reference to the canvas element we created earlier:

main.ts
const canvas = document.getElementById("canvas");

Device profile

Get the device profile:

main.ts
import { getDeviceProfile } from "@novorender/api";

const gpuTier = 2;
const deviceProfile = getDeviceProfile(gpuTier);

Imports

Remember we copied some resources from @novorender/api to our /public dir? that's where they come into play.

main.ts
import { View } from "@novorender/api";

const baseUrl = new URL("/novorender/api/", window.location.origin);
const imports = await View.downloadImports({ baseUrl });
Getting Error: Cannot find module '/novorender/api/shaders.js
In certain build environments or bundlers, you might encounter complaints about shaders.js not being found. In such cases, you will need to provide the shaders separately to resolve this issue.
// @ts-expect-error
import { shaders } from "@novorender/api/public/shaders";

const imports = await View.downloadImports({ baseUrl, shaders });
info

// @ts-expect-error is essential because TypeScript might incorrectly assume that the @novorender/api doesn't expose this module, even though it does and this will tell TypeScript to suppress that error from being reported.

Creating View

With all the necessary components in place, we can proceed to create and run our view:

main.ts
import { View } from "@novorender/api";

const view = new View(canvas, deviceProfile, imports);
await view.run();

Finally, call the View.dispose method to clean up the view's GPU resources.

main.ts
view.dispose();

Let's consolidate all the steps and components we've discussed and see the result:

main.ts
import { View, getDeviceProfile } from "@novorender/api";

const canvas = document.getElementById("canvas") as HTMLCanvasElement;

async function main(canvas: HTMLCanvasElement) {
const gpuTier = 2;
const deviceProfile = getDeviceProfile(gpuTier);
const baseUrl = new URL("/novorender/api/", window.location.origin);
const imports = await View.downloadImports({ baseUrl });
const view = new View(canvas, deviceProfile, imports);
await view.run();
view.dispose();
}

main(canvas);
tip

Avoid deep imports! Everything you need should be available from the package root: @novorender/api.

Start the development server by running the following command:

npm run dev
tip

Furthermore, you can run the project we've built above directly by clicking the button below:

Open in StackBlitz

Upon successful execution, you should now see an image with a subtle gray gradient: application screenshot initial view

Let's make it a little more interactive by modifying renderState and adding a RenderStateGrid.

main.ts
...
const view = new View(canvas, deviceProfile, imports);
view.modifyRenderState({ grid: { enabled: true } });
await view.run();
...

application screenshot grid view

info

You can learn more about the RenderState here and experiment with it by adding or modifying some of its properties. This will help you better understand how to customize rendering behaviors.

tip

The view already has camera controller built in so you can interact with the view by holding down the left-click on your mouse or trackpad. Alternatively, you can use the movement keys and WASD to navigate and move the camera around in the view.

info

The view will automatically resize your canvas' pixel size when the css layout and/or the render state output settings changes.

Wrapping up

🎉 Congratulations! You have successfully learned how to set up Novorender Web API. To gain a more comprehensive understanding of Novorender's capabilities, please explore the following resources:

Interactive Guides

Take a look at our interactive guides, which provide in-depth insights into various features.

Documentation

Refer to the reference documentation for detailed information on different methods and classes.

Code Samples

Visit the GitHub repository containing sample projects using different module bundlers for further practical examples.


These resources will help you harness the full potential of Novorender.

Next steps

Take a look at the following guides to learn how to load scenes using the Data JS API and perform basic measurements using the Measure Module:

  1. Loading Scenes with Data JS API: This guide will walk you through the process of loading scenes using the Data JS API, allowing you to access and manipulate your 3D data efficiently.

  2. Basic Measurements with Measure Module: Explore this guide to learn how to perform fundamental measurements using the Measure Module, enabling you to analyze and extract valuable data from your 3D models.

These guides will provide you with valuable insights and practical knowledge to effectively work with scenes and perform measurements in your Novorender projects.