Getting Started with Vite
Setup
To begin, let's initiate a new Vite + React + TypeScript project from scratch:
- NPM
- Yarn
- pnpm
# npm 6.x
npm create vite@latest my-novorender-app --template react-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest my-novorender-app -- --template react-ts
yarn create vite my-novorender-app --template react-ts
pnpm create vite my-novorender-app --template react-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
- Yarn
- pnpm
npm install
yarn install
pnpm 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
┣ ┣ 📂 assets
┃ ┣ ┗ 📜 react.svg ⛔
┃ ┣ 📜 App.css
┃ ┣ 📜 App.tsx
┃ ┣ 📜 index.css
┃ ┣ 📜 main.tsx
┃ ┗ 📜 vite-env.d.ts
┣ 📜 .gitignore
┣ 📜 index.html
┣ 📜 package.json
┣ 📜 vite.config.ts
┗ 📜 tsconfig.json
...
Let's tidy up this project by removing redundant files and unnecessary boilerplate code. First, delete the files marked with the ⛔ symbol. Next, clear out any styles from App.css
and index.css
, and ensure the return
statement in App.tsx
is free of any JSX.
Installing Web API
Run the following command to install the Novorender Web API package:
- npm
- Yarn
- pnpm
npm i --save @novorender/api
yarn add @novorender/api
pnpm add @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.
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:
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 });
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.
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:
-
A secure context. In practice, this means HTTP on localhost (for debugging only) and HTTPS everywhere else, including LAN.
-
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.
Place the following properties inside the vite.config.ts
file:
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
}
}
the file should now look like this:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
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.
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.
-
MIME type
text/javascript
for javascript files andapplication/wasm
for web assembly files. -
Any resources loaded from a separate domain has be configured with CORS to allow your domain.
-
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:
function App() {
return (
<>
<canvas style={{ width: "100%", height: "100%" }}></canvas>
</>
);
}
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, insert the following code in the same component to obtain a reference to the canvas element we created earlier:
...
function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
return (
<>
<canvas ref={canvasRef} style={{ width: "100%", height: "100%" }}></canvas>
</>
);
}
...
Device profile
Get the device profile:
...
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.
...
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
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 });
// @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:
...
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.
...
view.dispose();
...
Let's consolidate all the steps and components we've discussed and see the result:
import { useEffect, useRef } from "react";
import { View, getDeviceProfile } from "@novorender/api";
const gpuTier = 2;
const deviceProfile = getDeviceProfile(gpuTier);
const baseUrl = new URL("/novorender/api/", window.location.origin);
function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
if (canvasRef.current) {
main(canvasRef.current);
}
}, []);
async function main(canvas: HTMLCanvasElement) {
const imports = await View.downloadImports({ baseUrl });
const view = new View(canvas, deviceProfile, imports);
await view.run();
view.dispose();
}
return (
<>
<canvas ref={canvasRef} style={{ width: "100%", height: "100%" }}></canvas>
</>
);
}
export default App;
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
- Yarn
- pnpm
npm run dev
yarn dev
pnpm run dev
Upon successful execution, you should now see an image with a subtle gray gradient:
Let's make it a little more interactive by modifying renderState
and adding a RenderStateGrid
.
...
const view = new View(canvas, deviceProfile, imports);
view.modifyRenderState({ grid: { enabled: true } });
await view.run();
...
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.
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.
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:
-
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.
-
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.