Workers SDK Issue Reports

← Back to Dashboard

#6353 Relative imports from nested main in base_dir breaks

Download Reproduction
Recommendation:KEEP OPEN
Difficulty:easy
Reasoning:

Bug confirmed in wrangler 4.60.0. PR #6971 attempted but failed to fix. modulesRoot in miniflare/index.ts incorrectly set to entry dir instead of base_dir, breaking module resolution.

Suggested Action:

Implement one-line fix: use config.bundle.entry.moduleRoot instead of path.dirname(scriptPath)

Analysis Report

Issue Review: cloudflare/workers-sdk#6353

Summary

Relative imports from a nested main entry point fail when using base_dir with no_bundle = true because the modulesRoot for Miniflare is incorrectly set to the entry point's directory instead of the configured base_dir.

Findings

  • Created: 2024-07-28
  • Updated: 2025-10-30
  • Version: 3.67.1 -> 4.60.0 (current)
  • Component: Wrangler (deployment-bundle, dev/miniflare)
  • Labels: bug
  • Comments: 0

Key Evidence

  • PR #6971 explicitly states it "was supposed to fix (but doesn't) #6353"
  • Issue #6353 is NOT mentioned in any changelog
  • Bug successfully reproduced with wrangler 4.60.0 - exact same error as reported
  • Root cause identified in source code

Reproduction Results

Using the exact configuration from the issue with wrangler 4.60.0:

✘ [ERROR] service core:user:issue-6353-repro: Uncaught Error: internal error
✘ [ERROR] The Workers runtime failed to start.

Recommendation

Status: KEEP OPEN

Reasoning: The bug is confirmed to still exist in the latest version. PR #6971 attempted but failed to fix this issue. The root cause has been identified in the source code.

Action: Implement the fix described below.


Root Cause Analysis

The issue is in how Miniflare's modulesRoot is constructed in packages/wrangler/src/dev/miniflare/index.ts:179:

const modulesRoot = path.dirname(scriptPath);

Where scriptPath is the resolved entry point path (e.g., /path/to/some/base_dir/nested/index.js).

The Problem Flow:

  1. Entry Configuration (entry.ts:~133):

    • moduleRoot is correctly set from config.base_dir (e.g., ./some/base_dir)
    • This is used by findAdditionalModules() to discover modules
  2. Module Discovery (find-additional-modules.ts:~74):

    • Scans entry.moduleRoot (the base_dir)
    • Module foo.js is found and added with name foo.js (relative to base_dir)
    • Entry point filter (line ~77): filter((m) => m.name !== relativeEntryPoint) - uses path.relative(entry.moduleRoot, entry.file) which gives nested/index.js
  3. Miniflare Module Setup (miniflare/index.ts:179-193):

    const modulesRoot = path.dirname(scriptPath);  // = /path/to/some/base_dir/nested/
    const sourceOptions: SourceOptions = {
      modulesRoot,
      modules: [
        { type: ..., path: scriptPath, contents: entrypointSource },  // Full path
        ...modules.map((module) => ({
          type: ...,
          path: path.resolve(modulesRoot, module.name),  // WRONG!
          contents: module.content,
        })),
      ],
    };
    
  4. The Mismatch:

    • Entry point path: /path/to/some/base_dir/nested/index.js
    • modulesRoot: /path/to/some/base_dir/nested/ (dirname of entry point)
    • Additional module foo.js resolved path: /path/to/some/base_dir/nested/foo.js
    • But it should be: /path/to/some/base_dir/foo.js
  5. At Runtime:

    • Import ../foo.js from nested/index.js tries to resolve /foo.js (going up one level)
    • But workerd's virtual filesystem has foo.js at /nested/foo.js (wrong location)
    • Result: "internal error" because the module can't be found

Proposed Solution

Option 1: Use entry.moduleRoot for modulesRoot (Recommended)

In packages/wrangler/src/dev/miniflare/index.ts, change line 179 from:

const modulesRoot = path.dirname(scriptPath);

To:

const modulesRoot = config.bundle.entry.moduleRoot;

This ensures the modulesRoot matches where additional modules were discovered.

Option 2: Calculate relative entry point path

Alternatively, ensure the entry point's module path is relative to the moduleRoot:

const modulesRoot = config.bundle.entry.moduleRoot;
const relativeEntryPath = path.relative(modulesRoot, scriptPath);

const sourceOptions: SourceOptions = {
  modulesRoot,
  modules: [
    {
      type: ModuleTypeToRuleType[config.bundle.type],
      path: path.resolve(modulesRoot, relativeEntryPath),  // or just use scriptPath directly
      contents: entrypointSource,
    },
    ...modules.map((module) => ({
      type: ModuleTypeToRuleType[module.type ?? "esm"],
      path: path.resolve(modulesRoot, module.name),
      contents: module.content,
    })),
  ],
};

Files to Modify

  1. packages/wrangler/src/dev/miniflare/index.ts - Primary fix location

    • Line ~179: Change modulesRoot to use config.bundle.entry.moduleRoot
  2. Potential secondary locations (may need alignment):

    • packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts - For deploy path (if same issue exists)

Implementation Difficulty: Easy

Justification:

  • Single line change in most cases
  • The correct value (entry.moduleRoot) is already available in the config.bundle.entry object
  • No architectural changes required
  • Existing test case was added in PR #6971, just need to verify it passes

Testing Recommendations

  1. Verify existing test passes: PR #6971 added a relevant test case - ensure it now passes with the fix

  2. Manual reproduction test:

    - some/
      - base_dir/
        - nested/
          - index.js (imports ../foo.js)
        - foo.js
    - wrangler.toml (main = "./some/base_dir/nested/index.js", base_dir = "./some/base_dir")
    
  3. Additional test cases:

    • Deeply nested entry points (e.g., base_dir/a/b/c/index.js importing ../../shared/util.js)
    • Entry point at base_dir root (should still work)
    • Mix of relative imports going up and down directories
  4. Deploy path verification: Ensure the fix doesn't break wrangler deploy with the same configuration

Suggested Comment

This issue is confirmed as still occurring in wrangler 4.60.0. The root cause has been identified:

In packages/wrangler/src/dev/miniflare/index.ts, modulesRoot is set to path.dirname(scriptPath) (the entry point's directory), but it should use config.bundle.entry.moduleRoot (the configured base_dir). This causes additional modules to be placed in the wrong location in workerd's virtual filesystem, breaking relative imports.

The fix is a one-line change. Would a PR be welcome?

Notes & Feedback (0)

No notes yet.

Add Note