Configuring Path Aliases (#/*) for Imports in OpenCut package.json

OpenCut uses a dual-configuration strategy where the #/* alias is defined in both tsconfig.json for TypeScript compilation and package.json for ESM runtime resolution, mapping the virtual prefix to the src directory.

The OpenCut video editing application uses modern ES module path aliasing to maintain clean import statements across its React-based web interface. By configuring the #/* pattern in apps/web/package.json alongside the TypeScript configuration, the project eliminates brittle relative paths like ../../../lib/utils in favor of absolute-style imports. This setup leverages Node.js ESM import maps and Vite's bundler resolution to work seamlessly during both development and production builds.

Understanding the Dual Configuration Strategy

OpenCut implements the #/* alias through two complementary configuration files that must remain synchronized. This dual approach ensures that TypeScript can locate modules during compilation while the Node.js ESM loader resolves them correctly at runtime.

TypeScript Configuration (tsconfig.json)

The TypeScript compiler relies on the paths mapping in apps/web/tsconfig.json to understand the alias during type checking and IDE autocomplete operations. The configuration maps the #/* pattern to the ./src/* directory:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "paths": {
      "#/*": ["./src/*"],
      "@/*": ["./src/*"]
    }
  }
}

The moduleResolution: "bundler" setting is critical here, as it enables TypeScript to resolve import maps defined in package.json when working with modern build tools like Vite.

ESM Import Map (package.json)

The runtime resolution depends on the imports field in apps/web/package.json, which creates a Node.js ESM import map. This field mirrors the TypeScript configuration:

{
  "imports": {
    "#/*": "./src/*"
  }
}

When Vite processes the application, it reads this import map to transform bare specifiers like #/lib/utils.ts into actual file system paths relative to the package root. Both configurations must target the same ./src/* directory to prevent runtime errors that would occur if TypeScript and the bundler resolved modules differently.

Setting Up Path Aliases in OpenCut

To configure or modify the #/* alias in your OpenCut installation, follow these implementation steps:

  1. Update the TypeScript configuration in apps/web/tsconfig.json by adding or modifying the paths object under compilerOptions:

    {
      "compilerOptions": {
        "paths": {
          "#/*": ["./src/*"]
        }
      }
    }
  2. Declare the import map in apps/web/package.json using the imports field at the root level:

    {
      "imports": {
        "#/*": "./src/*"
      }
    }
  3. Restart the development server after saving both files. Vite reads the package.json import map only during initialization, so hot module replacement will not detect changes to the alias configuration.

Practical Usage Examples

With the alias active, components throughout apps/web/src/components/ui/ import utilities using the #/ prefix instead of relative traversal. The cn utility function from apps/web/src/lib/utils.ts is imported consistently across multiple components:

// In apps/web/src/components/ui/tooltip.tsx
import { cn } from "#/lib/utils.ts"

// In apps/web/src/components/ui/button.tsx  
import { cn } from "#/lib/utils.ts"

The sidebar component demonstrates complex aliasing patterns by importing multiple internal modules:

// In apps/web/src/components/ui/sidebar.tsx
import { cn } from "#/lib/utils.ts"
import { useIsMobile } from "#/hooks/use-mobile.ts"

These statements resolve to apps/web/src/lib/utils.ts and apps/web/src/hooks/use-mobile.ts respectively, regardless of the component's depth in the directory tree. This eliminates the need for calculations like ../../../../lib/utils when components are nested deeply within the UI hierarchy.

Benefits of Using the #/* Alias

Adopting the #/* convention in OpenCut provides three primary advantages for codebase maintenance:

  • Refactor-resistant imports: Moving a component from apps/web/src/components/ui/ to apps/web/src/components/features/ requires zero import statement changes, as the #/ prefix remains valid from any location within src.
  • Tooling consistency: Because both tsconfig.json and package.json define identical mappings, TypeScript language services in VS Code and the Vite bundler resolve modules to the same physical files, preventing "module not found" discrepancies between development and build time.
  • Clear dependency boundaries: The # prefix immediately signals internal project imports versus external npm packages, making it easier to distinguish between first-party utilities and third-party dependencies during code reviews.

Summary

  • OpenCut configures the #/* path alias in both apps/web/tsconfig.json (TypeScript paths) and apps/web/package.json (ESM imports).
  • Both files map #/* to ./src/*, enabling imports like import { cn } from "#/lib/utils.ts".
  • The configuration requires moduleResolution: "bundler" in TypeScript to work correctly with Vite.
  • Changes to either configuration file require a full dev server restart to take effect.
  • The alias appears throughout the UI components, including tooltip.tsx, button.tsx, and sidebar.tsx.

Frequently Asked Questions

Why does OpenCut use the #/* prefix instead of @/*?

OpenCut maintains both #/* and @/* aliases that point to the same ./src/* directory according to the tsconfig.json paths configuration. The # prefix follows the Node.js convention for internal package imports as defined in the ESM import maps specification, while @ is a common TypeScript convention. The # character specifically indicates subpath imports that are private to the package, making it ideal for internal application code that should not be exposed as a public API.

What happens if I only configure the alias in tsconfig.json but not package.json?

If you omit the imports field from apps/web/package.json, TypeScript will compile without errors and VS Code will provide correct autocomplete, but the application will fail at runtime with a "Cannot find module" error. This occurs because Vite's dev server relies on the Node.js ESM loader to resolve the #/ prefix, and without the import map declaration, the runtime does not know where to locate the requested modules.

Can I add additional path aliases beyond #/*?

Yes, you can extend both configuration files to support additional aliases. For example, to create a #/components/* alias that maps specifically to ./src/components/*, add the entry to both tsconfig.json paths and package.json imports. However, you must ensure that more specific aliases are listed before the general #/* wildcard in the imports object, as Node.js resolves imports in declaration order and stops at the first match.

Does this configuration work with OpenCut's production build?

The #/* alias functions identically in production builds because Vite processes the package.json import map during both development and bundling phases. The build pipeline statically analyzes the ESM imports and replaces the #/ specifiers with relative paths or bundled module IDs. As long as both configuration files remain synchronized, the alias resolution works consistently across vite dev, vite build, and preview modes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →