# three-stdlib packages the three.js examples directory as a real dependency

> The three.js examples directory is normally used by copying files into a project, and three-stdlib exists to make those helpers installable instead, built as a TypeScript library with parallel ESM and CommonJS output and named exports. It is a packaging fix rather than a new feature, and the interesting details are in the build script and in the dependency versions it still targets.

**pmndrs/three-stdlib** — 📚 Stand-alone library of threejs examples designed to run without transpilation in node & browser

- Repository: https://github.com/pmndrs/three-stdlib
- Website: https://npmjs.com/three-stdlib
- Stars: 863 · Forks: 146
- Language: JavaScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/pmndrs-three-stdlib

## The copy and paste habit this package replaces

The problem statement in the README is short and blunt. The `three/examples` files are usually regarded as something that you copy and paste into your project and adapt to your needs, and the README says that is not how people use it, and that this has caused numerous issues in the past.

Those issues are predictable once you look at what the examples directory actually is. It is a set of helper classes, controls, loaders and post-processing passes that live in the three.js repository but are not part of the published library's public API. Using them means copying source into your tree, where it stops receiving fixes, drifts from the version of three you have installed, and quietly becomes your code to maintain.

The solution offered here is to treat that directory as a package.

```text
Stand-alone version of [threejs/examples/jsm](https://github.com/mrdoob/three.js/tree/dev/examples/jsm) written in Typescript & built for ESM & CJS.
```

So three-stdlib is a mirror of the upstream examples, written in TypeScript, built for both module systems, and published to npm. The project is under the pmndrs organisation, and the maintainers listed in the manifest are Cody Bennett and Josh Ellis, with the author recorded as Paul Henschel, who is the author of the react-three-fiber work that this package serves.

The README states the motivation in a way that is worth repeating, because it is the actual argument for the project. The point is to allow more people who rely on these primitives to hold a small stake in them and to share the weight of maintaining it. That is a governance argument as much as a technical one, and it is why the package tracks the upstream examples closely rather than diverging into its own API.

## One package, two module systems, one exports map

The manifest is where the packaging decisions are visible, and the `exports` field is the interesting part.

```json
  "main": "./index.cjs",
  "module": "./index.js",
  "exports": {
    "types": "./index.d.ts",
    "require": "./index.cjs",
    "import": "./index.js"
  },
```

The `main` and `module` fields are the older, widely supported way of pointing a bundler or a runtime at two different files, and the `exports` field is the modern way of doing the same thing with an explicit map of conditions. The `types` condition comes first, which is the ordering TypeScript's resolution respects, and the `require` and `import` conditions cover a CommonJS consumer and an ES module consumer respectively.

The build produces both. The `build` script in the manifest removes the previous `dist` directory, runs the Vite build, then runs the TypeScript compiler in declaration-only mode, copies the declaration files and the metadata files into the output, and finally rewrites the published `package.json` with a small JSON editing step.

That last step is the one that explains the odd version field in the source manifest. The version there reads `0.0.0-semantic-released`, and the package is marked private. The release tooling replaces both at publish time, which is what the `sideEffects: false` flag and the semantic release configuration in the repository root are supporting.

The `lint` script is the other half of the developer story and it is a single command.

```json
    "lint": "tsc --noEmit"
```

Type checking without emitting, which for a type-heavy package like this one is the check that matters.

## Named exports, no globals, and a tree-shaking promise

The README lists the properties the package is trying to give you, and they are specific enough to check.

The first is a build system for ESM and CommonJS that is compatible with the browser, with workers and with Node. That third combination is worth pausing on, because the examples directory was written for a browser page, and moving the same code to a worker or to a server side rendering path is where module format and global assumptions cause trouble.

The second is that the code is class based, optimized for tree-shaking, has no globals, and provides exports instead of collections. The globals point is the substantive one. The upstream examples often assume a single global three.js instance, which is convenient in a script tag and fatal in a module graph, so a version without globals has to take its dependencies explicitly.

Exports instead of collections follows from that, and it is what changes the import style. The README's basic usage shows both forms, a namespace import for the whole collection and named imports for individual pieces.

```ts
// Export collection
import * as STDLIB from 'three-stdlib'
// Flatbundle
import { OrbitControls, ... } from 'three-stdlib'
```

For tree-shaking to work at all, the package has to declare that importing it has no side effects, and the manifest does exactly that with `"sideEffects": false`. That flag is a promise to the bundler: nothing in this package runs for its effect at import time, so a tree-shaking bundler can drop the helpers you do not use. If your bundler ignores the flag, you get the whole library in the bundle, and the whole point of the package is lost.

The third property is typesafety with simple annotation-like types, and the fourth is SemVer and npm managed dependencies, which is the answer to the version drift problem described at the start.

## Types and the dependency versions it still targets

The type story is short. The manifest points `types` at `./index.d.ts`, the build emits declarations with a separate TypeScript compiler invocation, and the package depends on `@types/three` for the three.js type definitions. The README describes the goal as typesafety with simple annotation-like types, which is a modest claim for a package whose value is that the helpers are typed rather than untyped JavaScript copied into your project.

The dependency versions are the part worth reading twice, because they are old. The development dependencies pin `three` at `^0.128.0` with a matching `@types/three`, TypeScript at `^4.7.4` and Vite at `^4.3.8`, and the runtime dependencies are `draco3d`, `fflate` and `potpack`, the last two being the compression utilities the loaders use.

The peer dependency is deliberately wide.

```json
  "peerDependencies": {
    "three": ">=0.128.0"
  },
```

So the library is developed and tested against a three.js release from a while back, while declaring that any three at or above that version will do at runtime. That is a sensible arrangement for a package whose helpers are mostly independent of the core, and it is also the arrangement that creates the risk. A helper written against an old three.js can break against a new one, and nothing in the manifest would stop it, because the peer range is a floor rather than a tested range.

The runtime dependencies also explain why this package exists as a package rather than as a folder of files. `draco3d` and `fflate` are the decoders and compressors that the geometry and texture loaders pull in, and `potpack` handles the compressed texture format. Copying an example file into a project means also working out those dependencies by hand, which is a large part of the friction the package removes.

## Semantic release decides the version number for you

The publishing section is short, and it explains the version numbers you will see on npm.

The README says the project uses `semantic-release-action` to deploy the package, and that because of this only certain commits trigger the action of creating a release. The list is explicit: a `chore` commit will not release a new version, a `fix:` commit will create a `0.0.x` version, a `feat:` commit will create a `0.x.0` version, and a `BREAKING CHANGE:` commit will create a `x.0.0` version.

That table is a compact statement of the versioning policy, and it has a direct consequence for anyone depending on the package. The version number is derived from the commit message, so the decision to publish is made by whoever writes the commit, not by a maintainer pressing a button. The `chore` bucket is the escape hatch for the changes that do not warrant a release, which is why the repository can carry formatting, dependency and tooling changes without moving the version.

The contributing section says the same thing from the other side. You fork and clone the repository, run `yarn install` to install dependencies, create a branch for your pull request, and the commit messages have to follow the conventional commits specification, with a link to that specification in the README.

The `yarn.lock` file in the repository root is the matching detail. The package is published to npm and developed with yarn, and the lockfile is what makes the development build reproducible.

The releases themselves are frequent patch releases in the v2 line, with v2.36.0 and v2.36.1 in 2025 and v2.35.16 shortly before them. The last push was on 2026-06-26, so the repository is not archived, though the most recent published version in the release list is from 2025.

## Installing it and importing the pieces you want

The installation is one command, and the package is published under the name `three-stdlib`.

```bash
npm install three-stdlib
```

What you get is a library of helpers rather than an application, so the first use is an import. The README shows the two shapes, and the choice between them is a question of how much of the library you want in your bundle.

```ts
// Export collection
import * as STDLIB from 'three-stdlib'
// Flatbundle
import { OrbitControls, ... } from 'three-stdlib'
```

The namespace import brings the whole collection in as one object, which is convenient while you are exploring and is what you want to avoid in a bundle you care about. The named import is the tree-shaking friendly form, and paired with the `sideEffects: false` flag it lets a bundler drop the helpers your scene does not use.

The `OrbitControls` in that example is the canonical one. It is a camera control that the examples directory has carried for years, and it is a good illustration of the package's value: it is not a highlight reel of new features, it is the set of classes people have been copying out of the three.js repository for a decade, now arriving through the package manager with a version number and type declarations.

The repository also carries a `.codesandbox/` directory, which is the sandbox environment for the examples, and a `vite.config.ts` plus a `tsconfig.json` for the build, with a `.prettierrc` for formatting.

For a team that has been copying these files, the migration is not exciting, but it is mechanical. Replace the copied files with an import, pin a version of three-stdlib, and let the package manager own the updates.

## Conclusion

three-stdlib fits a three.js project that has been copying files out of the examples directory and wants those helpers to arrive through the package manager with versions and type declarations attached. Skip it if you are happy maintaining your own copies, since the upstream examples still work, and expect to check compatibility when you upgrade three itself. Verify first that the version of three-stdlib you install declares support for the three.js version you run, that your bundler honours the sideEffects flag for tree-shaking, and that the peer dependency range covers the three release in your project.

## FAQ

### What is three-stdlib and what problem does it solve?

It is a stand-alone version of the three.js examples directory, written in TypeScript and built for ESM and CommonJS, published to npm. The README says the examples are usually treated as something to copy and paste into a project, that this is not how people use them, and that it has caused numerous issues in the past.

### How do I install three-stdlib and import a helper?

Run `npm install three-stdlib`, then import either the whole collection with `import * as STDLIB from 'three-stdlib'` or individual pieces with a named import such as `import { OrbitControls } from 'three-stdlib'`, which is the form that lets a bundler tree-shake the rest away.

### How are three-stdlib versions published?

The README says the project uses `semantic-release-action`, so only certain commits trigger a release: a `chore` commit does not release, a `fix:` commit creates a `0.0.x` version, a `feat:` commit creates a `0.x.0` version, and a `BREAKING CHANGE:` commit creates a `x.0.0` version.

### Does three-stdlib support both ES modules and CommonJS?

Yes. The manifest points `main` at `./index.cjs`, `module` at `./index.js`, and an `exports` map with `types`, `require` and `import` conditions, and the build script runs the Vite build and a declaration-only TypeScript compile to produce both outputs.

### Which version of three.js does three-stdlib need?

The peer dependency is `three` at `>=0.128.0`, so any three at or above that version is accepted at runtime, while the development dependencies pin `three` at `^0.128.0` with a matching `@types/three`. Because the peer range is a floor rather than a tested range, compatibility with a much newer three is worth checking in your own project.

## Sources

- [License: MIT](https://github.com/pmndrs/three-stdlib/blob/main/LICENSE)
- [pmndrs/three-stdlib on GitHub](https://github.com/pmndrs/three-stdlib)
- [Project website](https://npmjs.com/three-stdlib)
- [README](https://github.com/pmndrs/three-stdlib/blob/main/README.md)
- [Releases](https://github.com/pmndrs/three-stdlib/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pmndrs-three-stdlib
