# How Cordis Resolves Context Base URLs and Handles Path Resolution

> Learn how Cordis resolves context base URLs and handles paths using its mutable baseUrl property for efficient module imports and file system navigation.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-08-23

---

**Cordis resolves context base URLs through a mutable `baseUrl` property on the `Context` object, which serves as the reference point for all relative module imports and file-system paths throughout the framework.**

The Cordis framework implements a flexible, runtime-adjustable mechanism for path resolution. Every `Context` instance carries a `baseUrl` that plugins can read and update, ensuring that relative imports always resolve correctly regardless of where configuration files or entry points are located.

## The Context Base URL Mechanism

At the heart of Cordis path resolution lies the `Context` class defined in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts). The `baseUrl` field is declared in the `Context` interface and initialized to `undefined` in the constructor.

This mutable property allows the framework to establish a dynamic resolution context. Rather than relying on static configuration or environment variables, Cordis tracks resolution state directly on the context object that flows through every plugin.

## Setting the Initial Base URL

When the **loader** plugin instantiates, it accepts an optional `baseUrl` configuration option. If provided, the loader assigns it directly to the current context:

```typescript
if (config.baseUrl) {
  this.ctx.baseUrl = config.baseUrl
}

```

*Source:* [`packages/loader/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts)

This assignment typically occurs during application bootstrap. Users commonly set `baseUrl` to the directory of the main entry script:

```typescript
import { Context } from 'cordis'
import { Loader } from '@cordisjs/plugin-loader'

const ctx = new Context()
new Loader(ctx, { baseUrl: import.meta.url })

```

## Dynamic Base URL Updates via the Include Plugin

The **include** plugin demonstrates how Cordis handles nested configuration files. When loading a configuration file, the include plugin:

1. Resolves the config file path relative to the current `ctx.baseUrl`
2. Rewrites `ctx.baseUrl` to the directory containing that file

```typescript
this.filename = fileURLToPath(new URL(this.config.path, this.ctx.baseUrl))
this.ctx.baseUrl = new URL('.', pathToFileURL(this.filename)).href

```

*Source:* [`packages/include/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/include/src/index.ts)

This ensures that subsequent relative imports inside the configuration file resolve against the config file's directory rather than the original base URL. The context's resolution point shifts dynamically as the application structure is discovered.

## Module Import Resolution in the Loader Tree

When the loader imports a module by name, it constructs a `URL` object using the current `ctx.baseUrl`:

```typescript
return await import(/* @vite-ignore */new URL(name, this.ctx.baseUrl).href)

```

*Source:* [`packages/loader/src/config/tree.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/tree.ts)

If the module name is already an absolute URL, the base URL is ignored per standard `URL` constructor behavior. Otherwise, the relative name resolves against `ctx.baseUrl` to produce an absolute import path.

## Hot Module Replacement Path Handling

The HMR subsystem also depends on `ctx.baseUrl` for filesystem operations. It computes a module's directory (`baseDir`) using:

```typescript
fileURLToPath(new URL(config.base || '.', ctx.baseUrl))

```

*Source:* [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts)

This conversion from URL to filesystem path enables file-watching and recompilation to function correctly regardless of where the application was started.

## Complete Resolution Flow

Understanding how Cordis handles base URL resolution requires tracing the complete lifecycle:

- **Initialization**: Application starts with `ctx.baseUrl` as `undefined`
- **Loader setup**: If configured, `Loader` assigns the initial `baseUrl`
- **Configuration loading**: `Include` plugin may replace `baseUrl` with a config file's directory
- **Runtime imports**: All relative imports resolve against the current `ctx.baseUrl`
- **Development tools**: HMR and utilities read `ctx.baseUrl` for filesystem paths

## Practical Implementation Example

```typescript
import { Context } from 'cordis'
import { Loader } from '@cordisjs/plugin-loader'
import { Include } from '@cordisjs/plugin-include'

const ctx = new Context()

// 1. Establish initial base URL from entry script
new Loader(ctx, { baseUrl: import.meta.url })

// 2. Load config file—the include plugin adjusts baseUrl automatically
new Include(ctx, {
  path: './config.yml',  // resolved relative to initial baseUrl
})

// 3. After Include loads, ctx.baseUrl points to config.yml's directory

// 4. Import modules relative to the current baseUrl
await import(/* @vite-ignore */new URL('my-plugin.js', ctx.baseUrl).href)

```

## Summary

- **Mutable `baseUrl`**: The `Context` class in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) stores resolution state as a mutable property
- **Loader initialization**: [`packages/loader/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts) accepts and assigns the initial `baseUrl` option
- **Dynamic updates**: [`packages/include/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/include/src/index.ts) shifts `baseUrl` to config file directories
- **Import resolution**: [`packages/loader/src/config/tree.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/tree.ts) uses `new URL(name, ctx.baseUrl)` for module loading
- **HMR support**: [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts) derives filesystem paths from `ctx.baseUrl` for watching and recompilation

## Frequently Asked Questions

### What happens if no baseUrl is configured?

The `baseUrl` property remains `undefined` and relative URL resolution falls back to the current working directory or fails explicitly depending on the operation. The **include** plugin or explicit loader configuration typically provides the initial value before relative imports occur.

### Can plugins override baseUrl after initialization?

Yes. Any plugin with access to the context can read and write `ctx.baseUrl`. The **include** plugin demonstrates this pattern by updating the base URL when loading nested configuration files, ensuring subsequent imports resolve correctly.

### How does Cordis handle absolute versus relative paths?

Absolute URLs (those with protocols like `file://` or `https://`) pass through unchanged. Relative paths always resolve against `ctx.baseUrl` using the standard `URL` constructor, which ignores the base when the input is already absolute.

### Is baseUrl used for both ESM imports and filesystem operations?

Correct. ESM dynamic imports use `new URL(name, ctx.baseUrl).href` directly. Filesystem operations in HMR and other utilities convert the URL to a path using `fileURLToPath()` after resolution against `ctx.baseUrl`.