Skip to content

[Bug]: Dynamic import() with template literal resolves relative path against test file instead of source module #1207

@christiango

Description

@christiango

Version

System:
    OS: macOS 26.4.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 225.70 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Edge: 147.0.3912.86
    Safari: 26.4
  npmPackages:
    @rstest/core: ^0.9.9 => 0.9.9

Details

Description

When a bundled dependency contains a dynamic import() with a template literal (e.g., import(`./translations/${locale}/strings.json`)), rstest resolves the relative path against the test file's location instead of the source module's location. This causes ERR_MODULE_NOT_FOUND for files that exist in the dependency but not next to the test file.

This is a common pattern for i18n/localization — loading locale-specific JSON at runtime — and is a migration blocker from Jest for monorepos that use it.

Reproduction

A dependency exports a function that does a dynamic import with a template literal:

// lib-a/index.js
export const fetchStrings = (locale) =>
  import(`./translations/${locale}/strings.json`);
// lib-a/translations/en-us/strings.json
{ "greeting": "Hello" }

A test in a different package imports and calls it:

// app-b/src/app.test.js
import { fetchStrings } from 'lib-a';

test('fetchStrings resolves the JSON from lib-a', async () => {
  const strings = await fetchStrings('en-us');
  expect(strings.greeting).toBe('Hello');
});

Failing e2e test

I've prepared a branch with a failing e2e test that follows the existing e2e/externals/ conventions:

Branch: fix/dynamic-import-relative-path-e2e
Diff: main...fix/dynamic-import-relative-path-e2e

To run it locally:

git remote add christiango https://github.com/christiango/rstest.git
git fetch christiango fix/dynamic-import-relative-path-e2e
git checkout christiango/fix/dynamic-import-relative-path-e2e
pnpm install
cd e2e && pnpm rstest run externals/dynamicImportRelative.test.ts

Expected behavior

./translations/en-us/strings.json resolves relative to lib-a/index.js (the module containing the import() call), finding lib-a/translations/en-us/strings.json.

Actual behavior

rstest resolves the path relative to the test file (app-b/src/app.test.js), producing:

Error: Cannot find module '.../app-b/src/translations/en-us/strings.json'
    imported from .../node_modules/@rstest/core/dist/0~loadEsModule.js

Root cause analysis

This appears to be a known issue — there's a TODO comment in the source at packages/core/src/runtime/worker/loadEsModule.ts line 105:

// TODO: use module path instead of testPath
import.meta.resolve(specifier, pathToFileURL(testPath));

When defineRstestDynamicImport encounters a relative specifier that isn't in assetFiles (which is the case for template-literal dynamic imports since rspack can't statically resolve them), it falls back to import.meta.resolve(specifier, pathToFileURL(testPath)). This resolves the path relative to the test file instead of the originating dependency module.

Because rspack bundles everything into a single file, the originating module's identity is lost by the time the dynamic import runs at runtime. The importModuleDynamically callback's _referencer parameter points to the single bundle module, not the original source file that contained the import() call. So a straightforward fix using the referencer won't work — the resolution context would need to be preserved through the bundling step, likely via rspack's context module handling for template-literal imports.

Key source locations:

Reproduce link

christiango/rstest@main...fix/dynamic-import-relative-path-e2e)

Reproduce Steps

git remote add christiango https://github.com/christiango/rstest.git
git fetch christiango fix/dynamic-import-relative-path-e2e
git checkout christiango/fix/dynamic-import-relative-path-e2e
pnpm install
cd e2e && pnpm rstest run externals/dynamicImportRelative.test.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions