Adom 3D Viewer
Public Made by Adomby adom
The Babylon 9.5 engine behind every component page's 3D tab on wiki.adom.inc. Versioned ESM bundle with GLB loading, view cube, layers toolbar, ground shadows, and Z-up CAD framing.
3D Viewer
A standalone Svelte component that wraps Babylon.js with shared lighting, gray skybox, and arc-rotate camera controls (orbit, pan, zoom, WASD). No floor—just the canvas and skybox. Supports Z-up or Y-up and loading GLB/GLTF models by URL or via API.
How dist works
When you run npm run build, Vite compiles the library from src/ into the dist/ folder (bundled JavaScript and type definitions). The package is set up so that when another app installs this repo and does:
import { ThreeDViewer } from '3d-viewer';
Node resolves that to dist/index.js (see main, module, and exports in package.json). So the built dist/ is what other projects actually use—not the raw src/ files.
You must run npm run build in this repo before:
- Using
npm linkornpm install /path/to/3d-viewerin another app, or - Publishing to a registry (otherwise consumers get no or stale build).
If dist/ is missing or outdated, the other app’s import will fail or use old code.
This package includes a prepare script that runs npm run build after npm install. So when someone installs from GitHub (e.g. npm install github:adom-inc/3d-viewer), npm will build the library automatically and dist/ will be present—no need to commit dist/ or use a Vite alias in the consuming app.
Installation
From GitHub (recommended; prepare builds automatically):
npm install github:adom-inc/3d-viewer
From a local path (e.g. while developing the viewer):
cd /path/to/3d-viewer
npm install
npm run build
# then in your app:
npm install /path/to/3d-viewer
Or, after publishing, use the package name from your registry.
Peer dependencies (your app must have these):
svelte^4.0.0 or ^5.0.0@babylonjs/core^7.0.0@babylonjs/loaders^7.0.0
If your app doesn’t already depend on Babylon, install them:
npm install @babylonjs/core @babylonjs/loaders
Basic usage
Give the viewer a fixed height (it fills 100% width and height of its parent):
<script>
import { ThreeDViewer } from '3d-viewer';
</script>
<div class="viewer-wrapper">
<ThreeDViewer />
</div>
<style>
.viewer-wrapper {
width: 100%;
height: 400px; /* or 100vh, etc. */
}
</style>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
zUp |
boolean |
true |
Use Z-axis as up (true) or Y-axis (false, Babylon default). |
modelUrl |
string | undefined |
undefined |
Optional URL of a GLB/GLTF to load when the viewer is ready. |
showViewCube |
boolean |
true |
Show or hide the view cube in the corner. |
showGround |
boolean |
false |
Show a ground plane beneath the model for receiving shadows. Use setGroundVisible(true/false) at runtime to toggle. |
environmentUrl |
string | undefined |
undefined |
URL of a custom .env file for PBR reflections. A neutral studio environment is bundled by default — no setup needed. |
initialViewMode |
'perspective' | 'orthographic' |
'perspective' |
Initial camera projection. Use setProjectionMode() or getProjectionMode() at runtime to change or read. |
Example with props:
<ThreeDViewer
zUp={true}
modelUrl="/models/part.glb"
showViewCube={true}
showGround={true}
/>
Loading and clearing models
Use bind:this to get the component instance and call loadModel(url) or clearScene():
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
let modelUrl = '';
async function loadFromUrl() {
if (viewer && modelUrl) {
await viewer.loadModel(modelUrl);
}
}
function clear() {
if (viewer) {
viewer.clearScene();
}
}
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<div class="controls">
<input type="text" bind:value={modelUrl} placeholder="GLB/GLTF URL" />
<button on:click={loadFromUrl}>Load model</button>
<button on:click={clear}>Clear scene</button>
</div>
<style>
.viewer-wrapper {
width: 100%;
height: 400px;
}
.controls {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
}
</style>
loadModel(url: string): Promise<void>— Loads a GLB/GLTF fromurl. Adds meshes to the scene and shadow casters, then frames the camera. Can be called multiple times; the frame will encompass all loaded models.clearScene(): void— Removes all models loaded vialoadModel(and any from the initialmodelUrlprop). Does not remove the skybox, lighting, or content you added withaddContentRoot.frameModel(fillFraction?: number): void— Re-frames the camera to fit all content: loaded models plus any roots registered withaddContentRoot.fillFractiondefaults to1(fill viewport); use a larger value to zoom out.getScene(): Scene | null— Returns the Babylon scene. Use it to create or modify native objects (spheres, boxes, materials, etc.).getEngine(): Engine | null— Returns the Babylon engine.getCamera(): Camera | null— Returns the active camera.getShadowGenerator(): ShadowGenerator | null— Returns the shadow generator. CalladdShadowCaster(mesh)for custom meshes that should cast shadows (or useaddContentRootwith default options).addContentRoot(node: TransformNode, options?: { castShadows?: boolean; receiveShadows?: boolean }): void— Registers a root (e.g. a parent of spheres) so it is included inframeModel(). By default its meshes cast and receive shadows. Does not dispose onclearScene().removeContentRoot(node: TransformNode): void— Unregisters a content root from framing (does not dispose the node).setGroundVisible(visible: boolean): void— Show or hide the ground plane at runtime. UpdatesshowGroundand re-runs framing if there is content.goHome(): void— Jump the camera to the home view (front–top–right, like the view cube corner) and re-frame the model. No animation.setProjectionMode(ortho: boolean): void— Set camera to orthographic (true) or perspective (false).getProjectionMode(): 'perspective' \| 'orthographic'— Return the current camera projection.showDebugLayer(): Promise<void>— Opens the Babylon.js debug layer (inspector). Lazy-loads the inspector on first use.hideDebugLayer(): void— Closes the debug layer.toggleDebugLayer(): Promise<void>— Toggles the debug layer open or closed.
Example: add a button to toggle the inspector:
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<button on:click={() => viewer?.toggleDebugLayer()}>Toggle debug inspector</button>
<style>
.viewer-wrapper { width: 100%; height: 400px; }
</style>
Creating native Babylon objects (e.g. planets)
You can use the viewer without loading any GLB: get the scene, create meshes with Babylon’s MeshBuilder, then register a root and frame so the camera fits your content.
Your app already has @babylonjs/core as a peer dependency, so you can use MeshBuilder, StandardMaterial, Vector3, etc.
<script lang="ts">
import { ThreeDViewer } from '3d-viewer';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Color3 } from '@babylonjs/core/Maths/math.color';
let viewer;
let planetsRoot: TransformNode | null = null;
function addPlanets() {
const scene = viewer?.getScene();
if (!scene) return;
if (planetsRoot) {
viewer.removeContentRoot(planetsRoot);
planetsRoot.dispose();
planetsRoot = null;
}
const root = new TransformNode('PlanetsRoot', scene);
planetsRoot = root;
const sun = MeshBuilder.CreateSphere('sun', { diameter: 2 }, scene);
sun.parent = root;
const sunMat = new StandardMaterial('sunMat', scene);
sunMat.emissiveColor = new Color3(1, 0.9, 0.5);
sun.material = sunMat;
const earth = MeshBuilder.CreateSphere('earth', { diameter: 0.6 }, scene);
earth.parent = root;
earth.position = new Vector3(3, 0, 0);
const earthMat = new StandardMaterial('earthMat', scene);
earthMat.diffuseColor = new Color3(0.2, 0.4, 0.8);
earth.material = earthMat;
viewer.addContentRoot(root); // include in framing + shadows
viewer.frameModel();
}
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<button on:click={addPlanets}>Show planets</button>
<style>
.viewer-wrapper { width: 100%; height: 400px; }
</style>
Simpler variant without a named root (use the scene as parent):
// Create spheres directly in the scene
const scene = viewer.getScene();
if (!scene) return;
const sphere = MeshBuilder.CreateSphere('planet', { diameter: 1 }, scene);
sphere.position = new Vector3(0, 0, 0);
viewer.addContentRoot(sphere); // sphere is the root; it will be included in frameModel()
viewer.frameModel();
- Use
getScene()to create or modify any Babylon objects (meshes, lights, materials). - Use
addContentRoot(node)so that node (and its children) are included when you callframeModel()and so they participate in shadow casting/receiving by default. clearScene()only clears models loaded vialoadModel(); it does not dispose content you added withaddContentRoot(). Dispose your own nodes when you no longer need them.
Full page example
<script lang="ts">
import { ThreeDViewer } from '3d-viewer';
let viewer;
const defaultModel = 'https://example.com/assets/model.glb';
</script>
<main>
<div class="viewer-container">
<ThreeDViewer
bind:this={viewer}
zUp={true}
modelUrl={defaultModel}
showViewCube={true}
/>
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
height: 100vh;
}
.viewer-container {
flex: 1;
min-height: 0;
}
</style>
Camera controls
- Left drag — Orbit around target (or click-and-hold to set rotation point).
- Middle mouse drag — Orbit.
- Right drag — Pan.
- Scroll — Zoom (zoom-to-cursor when supported).
- WASD — Move target.
- Q / E — Move target down / up.
- Space — Continuous orbit.
- Caps Lock — Precision (slower) mode.
Exposing the API to your app (ground, home, view mode)
Use bind:this to get the viewer instance, then call the methods from your own buttons or logic:
- Ground: Set initial state with the
showGroundprop, or toggle at runtime withviewer.setGroundVisible(true)/viewer.setGroundVisible(false). - Home: Call
viewer.goHome()to jump to the front–top–right view and re-frame the model. - View mode: Set initial projection with the
initialViewModeprop ('perspective'or'orthographic'). At runtime useviewer.setProjectionMode(true)for orthographic,viewer.setProjectionMode(false)for perspective, andviewer.getProjectionMode()to read the current mode.
Example:
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
</script>
<ThreeDViewer bind:this={viewer} showGround={false} initialViewMode="perspective" />
<button on:click={() => viewer?.setGroundVisible(true)}>Show ground</button>
<button on:click={() => viewer?.setGroundVisible(false)}>Hide ground</button>
<button on:click={() => viewer?.goHome()}>Home</button>
<button on:click={() => viewer?.setProjectionMode(true)}>Orthographic</button>
<button on:click={() => viewer?.setProjectionMode(false)}>Perspective</button>
Types / .d.ts — You don't add a hand-written .d.ts in source. The build (Vite + Svelte) emits dist/index.d.ts from your entry and component. With package.json "types": "./dist/index.d.ts", installers get types for the component and its instance (including the new methods). Ensure npm run build has been run (or they install from GitHub so prepare runs it).
Coordinate system and viewer quirks
For detailed context on Z-up vs Y-up, how Fusion exports Z-up and KiCad Y-up by default, and other quirks (SpotLight-only shadows, model-relative lighting, view cube always Z-up, etc.), see VIEWER_CONTEXT.md. That doc is written for AIs and maintainers.
Notes
- The viewer uses Babylon’s default CDN for environment/skybox assets; no extra static files are required.
- The component owns a single Babylon engine/scene per instance; on destroy it stops the render loop and disposes the engine and scene.
Publishing
The package already has prepublishOnly: "npm run build", so npm publish builds the library first and consumers get an up-to-date dist/. You do not need to commit dist/ to git.
# 3D Viewer
A standalone Svelte component that wraps Babylon.js with shared lighting, gray skybox, and arc-rotate camera controls (orbit, pan, zoom, WASD). No floor—just the canvas and skybox. Supports Z-up or Y-up and loading GLB/GLTF models by URL or via API.
## How `dist` works
When you run `npm run build`, Vite compiles the library from `src/` into the **`dist/`** folder (bundled JavaScript and type definitions). The package is set up so that when another app installs this repo and does:
```js
import { ThreeDViewer } from '3d-viewer';
```
Node resolves that to **`dist/index.js`** (see `main`, `module`, and `exports` in `package.json`). So **the built `dist/` is what other projects actually use**—not the raw `src/` files.
You must run `npm run build` in this repo before:
- Using `npm link` or `npm install /path/to/3d-viewer` in another app, or
- Publishing to a registry (otherwise consumers get no or stale build).
If `dist/` is missing or outdated, the other app’s import will fail or use old code.
This package includes a **`prepare`** script that runs **`npm run build`** after `npm install`. So when someone installs from GitHub (e.g. `npm install github:adom-inc/3d-viewer`), npm will build the library automatically and `dist/` will be present—no need to commit `dist/` or use a Vite alias in the consuming app.
## Installation
**From GitHub** (recommended; `prepare` builds automatically):
```bash
npm install github:adom-inc/3d-viewer
```
**From a local path** (e.g. while developing the viewer):
```bash
cd /path/to/3d-viewer
npm install
npm run build
# then in your app:
npm install /path/to/3d-viewer
```
Or, after publishing, use the package name from your registry.
**Peer dependencies** (your app must have these):
- `svelte` ^4.0.0 or ^5.0.0
- `@babylonjs/core` ^7.0.0
- `@babylonjs/loaders` ^7.0.0
If your app doesn’t already depend on Babylon, install them:
```bash
npm install @babylonjs/core @babylonjs/loaders
```
## Basic usage
Give the viewer a fixed height (it fills 100% width and height of its parent):
```svelte
<script>
import { ThreeDViewer } from '3d-viewer';
</script>
<div class="viewer-wrapper">
<ThreeDViewer />
</div>
<style>
.viewer-wrapper {
width: 100%;
height: 400px; /* or 100vh, etc. */
}
</style>
```
## Props
| Prop | Type | Default | Description |
|------------------|-------------------|---------|-------------|
| `zUp` | `boolean` | `true` | Use Z-axis as up (true) or Y-axis (false, Babylon default). |
| `modelUrl` | `string \| undefined` | `undefined` | Optional URL of a GLB/GLTF to load when the viewer is ready. |
| `showViewCube` | `boolean` | `true` | Show or hide the view cube in the corner. |
| `showGround` | `boolean` | `false` | Show a ground plane beneath the model for receiving shadows. Use `setGroundVisible(true/false)` at runtime to toggle. |
| `environmentUrl` | `string \| undefined` | `undefined` | URL of a custom `.env` file for PBR reflections. A neutral studio environment is bundled by default — no setup needed. |
| `initialViewMode` | `'perspective' \| 'orthographic'` | `'perspective'` | Initial camera projection. Use `setProjectionMode()` or `getProjectionMode()` at runtime to change or read. |
Example with props:
```svelte
<ThreeDViewer
zUp={true}
modelUrl="/models/part.glb"
showViewCube={true}
showGround={true}
/>
```
## Loading and clearing models
Use `bind:this` to get the component instance and call `loadModel(url)` or `clearScene()`:
```svelte
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
let modelUrl = '';
async function loadFromUrl() {
if (viewer && modelUrl) {
await viewer.loadModel(modelUrl);
}
}
function clear() {
if (viewer) {
viewer.clearScene();
}
}
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<div class="controls">
<input type="text" bind:value={modelUrl} placeholder="GLB/GLTF URL" />
<button on:click={loadFromUrl}>Load model</button>
<button on:click={clear}>Clear scene</button>
</div>
<style>
.viewer-wrapper {
width: 100%;
height: 400px;
}
.controls {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
}
</style>
```
- **`loadModel(url: string): Promise<void>`** — Loads a GLB/GLTF from `url`. Adds meshes to the scene and shadow casters, then frames the camera. Can be called multiple times; the frame will encompass all loaded models.
- **`clearScene(): void`** — Removes all models loaded via `loadModel` (and any from the initial `modelUrl` prop). Does not remove the skybox, lighting, or content you added with `addContentRoot`.
- **`frameModel(fillFraction?: number): void`** — Re-frames the camera to fit all content: loaded models plus any roots registered with `addContentRoot`. `fillFraction` defaults to `1` (fill viewport); use a larger value to zoom out.
- **`getScene(): Scene | null`** — Returns the Babylon scene. Use it to create or modify native objects (spheres, boxes, materials, etc.).
- **`getEngine(): Engine | null`** — Returns the Babylon engine.
- **`getCamera(): Camera | null`** — Returns the active camera.
- **`getShadowGenerator(): ShadowGenerator | null`** — Returns the shadow generator. Call `addShadowCaster(mesh)` for custom meshes that should cast shadows (or use `addContentRoot` with default options).
- **`addContentRoot(node: TransformNode, options?: { castShadows?: boolean; receiveShadows?: boolean }): void`** — Registers a root (e.g. a parent of spheres) so it is included in `frameModel()`. By default its meshes cast and receive shadows. Does not dispose on `clearScene()`.
- **`removeContentRoot(node: TransformNode): void`** — Unregisters a content root from framing (does not dispose the node).
- **`setGroundVisible(visible: boolean): void`** — Show or hide the ground plane at runtime. Updates `showGround` and re-runs framing if there is content.
- **`goHome(): void`** — Jump the camera to the home view (front–top–right, like the view cube corner) and re-frame the model. No animation.
- **`setProjectionMode(ortho: boolean): void`** — Set camera to orthographic (`true`) or perspective (`false`).
- **`getProjectionMode(): 'perspective' \| 'orthographic'`** — Return the current camera projection.
- **`showDebugLayer(): Promise<void>`** — Opens the Babylon.js debug layer (inspector). Lazy-loads the inspector on first use.
- **`hideDebugLayer(): void`** — Closes the debug layer.
- **`toggleDebugLayer(): Promise<void>`** — Toggles the debug layer open or closed.
Example: add a button to toggle the inspector:
```svelte
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<button on:click={() => viewer?.toggleDebugLayer()}>Toggle debug inspector</button>
<style>
.viewer-wrapper { width: 100%; height: 400px; }
</style>
```
## Creating native Babylon objects (e.g. planets)
You can use the viewer without loading any GLB: get the scene, create meshes with Babylon’s `MeshBuilder`, then register a root and frame so the camera fits your content.
Your app already has `@babylonjs/core` as a peer dependency, so you can use `MeshBuilder`, `StandardMaterial`, `Vector3`, etc.
```svelte
<script lang="ts">
import { ThreeDViewer } from '3d-viewer';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Color3 } from '@babylonjs/core/Maths/math.color';
let viewer;
let planetsRoot: TransformNode | null = null;
function addPlanets() {
const scene = viewer?.getScene();
if (!scene) return;
if (planetsRoot) {
viewer.removeContentRoot(planetsRoot);
planetsRoot.dispose();
planetsRoot = null;
}
const root = new TransformNode('PlanetsRoot', scene);
planetsRoot = root;
const sun = MeshBuilder.CreateSphere('sun', { diameter: 2 }, scene);
sun.parent = root;
const sunMat = new StandardMaterial('sunMat', scene);
sunMat.emissiveColor = new Color3(1, 0.9, 0.5);
sun.material = sunMat;
const earth = MeshBuilder.CreateSphere('earth', { diameter: 0.6 }, scene);
earth.parent = root;
earth.position = new Vector3(3, 0, 0);
const earthMat = new StandardMaterial('earthMat', scene);
earthMat.diffuseColor = new Color3(0.2, 0.4, 0.8);
earth.material = earthMat;
viewer.addContentRoot(root); // include in framing + shadows
viewer.frameModel();
}
</script>
<div class="viewer-wrapper">
<ThreeDViewer bind:this={viewer} />
</div>
<button on:click={addPlanets}>Show planets</button>
<style>
.viewer-wrapper { width: 100%; height: 400px; }
</style>
```
Simpler variant without a named root (use the scene as parent):
```svelte
// Create spheres directly in the scene
const scene = viewer.getScene();
if (!scene) return;
const sphere = MeshBuilder.CreateSphere('planet', { diameter: 1 }, scene);
sphere.position = new Vector3(0, 0, 0);
viewer.addContentRoot(sphere); // sphere is the root; it will be included in frameModel()
viewer.frameModel();
```
- Use **`getScene()`** to create or modify any Babylon objects (meshes, lights, materials).
- Use **`addContentRoot(node)`** so that node (and its children) are included when you call **`frameModel()`** and so they participate in shadow casting/receiving by default.
- **`clearScene()`** only clears models loaded via **`loadModel()`**; it does not dispose content you added with **`addContentRoot()`**. Dispose your own nodes when you no longer need them.
## Full page example
```svelte
<script lang="ts">
import { ThreeDViewer } from '3d-viewer';
let viewer;
const defaultModel = 'https://example.com/assets/model.glb';
</script>
<main>
<div class="viewer-container">
<ThreeDViewer
bind:this={viewer}
zUp={true}
modelUrl={defaultModel}
showViewCube={true}
/>
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
height: 100vh;
}
.viewer-container {
flex: 1;
min-height: 0;
}
</style>
```
## Camera controls
- **Left drag** — Orbit around target (or click-and-hold to set rotation point).
- **Middle mouse drag** — Orbit.
- **Right drag** — Pan.
- **Scroll** — Zoom (zoom-to-cursor when supported).
- **WASD** — Move target.
- **Q / E** — Move target down / up.
- **Space** — Continuous orbit.
- **Caps Lock** — Precision (slower) mode.
## Exposing the API to your app (ground, home, view mode)
Use `bind:this` to get the viewer instance, then call the methods from your own buttons or logic:
- **Ground:** Set initial state with the `showGround` prop, or toggle at runtime with `viewer.setGroundVisible(true)` / `viewer.setGroundVisible(false)`.
- **Home:** Call `viewer.goHome()` to jump to the front–top–right view and re-frame the model.
- **View mode:** Set initial projection with the `initialViewMode` prop (`'perspective'` or `'orthographic'`). At runtime use `viewer.setProjectionMode(true)` for orthographic, `viewer.setProjectionMode(false)` for perspective, and `viewer.getProjectionMode()` to read the current mode.
Example:
```svelte
<script>
import { ThreeDViewer } from '3d-viewer';
let viewer;
</script>
<ThreeDViewer bind:this={viewer} showGround={false} initialViewMode="perspective" />
<button on:click={() => viewer?.setGroundVisible(true)}>Show ground</button>
<button on:click={() => viewer?.setGroundVisible(false)}>Hide ground</button>
<button on:click={() => viewer?.goHome()}>Home</button>
<button on:click={() => viewer?.setProjectionMode(true)}>Orthographic</button>
<button on:click={() => viewer?.setProjectionMode(false)}>Perspective</button>
```
**Types / .d.ts** — You don't add a hand-written `.d.ts` in source. The build (Vite + Svelte) emits `dist/index.d.ts` from your entry and component. With `package.json` `"types": "./dist/index.d.ts"`, installers get types for the component and its instance (including the new methods). Ensure `npm run build` has been run (or they install from GitHub so `prepare` runs it).
## Coordinate system and viewer quirks
For detailed context on **Z-up vs Y-up**, how Fusion exports Z-up and KiCad Y-up by default, and other quirks (SpotLight-only shadows, model-relative lighting, view cube always Z-up, etc.), see **[VIEWER_CONTEXT.md](./src/VIEWER_CONTEXT.md)**. That doc is written for AIs and maintainers.
## Notes
- The viewer uses Babylon’s default CDN for environment/skybox assets; no extra static files are required.
- The component owns a single Babylon engine/scene per instance; on destroy it stops the render loop and disposes the engine and scene.
## Publishing
The package already has **`prepublishOnly`: `"npm run build"`**, so `npm publish` builds the library first and consumers get an up-to-date `dist/`. You do not need to commit `dist/` to git.