Skip to content

fix(bundle): deduplicate symlinked dependencies in luaBundle output#1684

Open
ChouUn wants to merge 2 commits intoTypeScriptToLua:masterfrom
ChouUn:fix/paths
Open

fix(bundle): deduplicate symlinked dependencies in luaBundle output#1684
ChouUn wants to merge 2 commits intoTypeScriptToLua:masterfrom
ChouUn:fix/paths

Conversation

@ChouUn
Copy link

@ChouUn ChouUn commented Feb 26, 2026

Fixes #1683

Problem

When using luaBundle in a pnpm/yarn workspace monorepo, the same package gets bundled multiple times under different module keys because symlinked dependencies resolve to different filesystem paths.

For example, in a workspace where pkg-c depends on both pkg-a and pkg-b, and pkg-b also depends on pkg-a, the bundle output contains pkg-a twice:

["lua_modules.pkg-a.dist.index"] = function(...)           -- direct dep
["lua_modules.pkg-b.node_modules.pkg-a.dist.index"] = ...  -- transitive dep (same file!)

This happens because pnpm creates symlinks like:

packages/pkg-c/node_modules/pkg-a -> ../../pkg-a
packages/pkg-b/node_modules/pkg-a -> ../../pkg-a   (same real path)

The existing deduplication in resolvedFiles and processedDependencies uses string-based path comparison, so different symlink paths for the same physical file bypass deduplication.

Solution

Add canonicalizeDependencyPath() in ResolutionContext which uses fs.realpathSync() to detect when different symlink paths point to the same physical file. The first-seen path becomes the canonical path, ensuring each file appears only once in the bundle.

This preserves the existing symlinks: false setting in enhanced-resolve (needed for node_modules path detection) while adding symlink-aware deduplication at the dependency processing level.

Changes

  • src/transpilation/resolve.ts: Add realPathMap and canonicalizeDependencyPath() to ResolutionContext
  • New test fixture: project-with-symlinked-dependency/ (symlink created dynamically in test for cross-platform compatibility)
  • New test: "bundle should not contain duplicate files from symlinked dependencies"

All 44 existing module-resolution tests pass with zero regressions.

When using pnpm/yarn workspaces, the same physical package can be
referenced through different symlink paths (e.g. node_modules/pkg-a
and node_modules/pkg-b/node_modules/pkg-a both pointing to the same
directory). TSTL's existing deduplication used string-based path
comparison, causing the same module to be bundled multiple times
under different module keys.

Add canonicalizeDependencyPath() which uses fs.realpathSync() to
detect when different symlink paths resolve to the same physical file,
ensuring each file appears only once in the bundle output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 26, 2026 21:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where luaBundle creates duplicate modules when using symlinked dependencies in pnpm/yarn workspace monorepos. The solution adds symlink-aware deduplication by tracking the real filesystem paths of dependencies and ensuring each physical file appears only once in the bundle.

Changes:

  • Adds canonicalizeDependencyPath() method in ResolutionContext to resolve symlinks to their real paths and map them to the first-seen path
  • Adds test fixture and test case to verify symlinked dependencies are deduplicated
  • Integrates canonicalization into the existing dependency resolution flow

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/transpilation/resolve.ts Adds realPathMap field and canonicalizeDependencyPath() method to detect and deduplicate symlinked dependencies by resolving them to canonical paths
test/transpile/module-resolution.spec.ts Adds test suite with beforeAll/afterAll hooks to dynamically create symlinks and verify no duplication occurs in bundle output
test/transpile/module-resolution/project-with-symlinked-dependency/main.ts Test entry point that imports both directly and transitively from shared-lib via consumer module
test/transpile/module-resolution/project-with-symlinked-dependency/tsconfig.json TypeScript configuration for test fixture
test/transpile/module-resolution/project-with-symlinked-dependency/shared-lib/index.lua Shared library implementation that should appear only once in bundle
test/transpile/module-resolution/project-with-symlinked-dependency/shared-lib/index.d.ts TypeScript declarations for shared library

- Add fs.mkdirSync safety check before symlink creation
- Add fs.existsSync guard before lstatSync in afterAll cleanup
- Use literal hyphen in regex instead of unescaped dot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

luaBundle includes duplicate modules when using pnpm/yarn workspace symlinks

2 participants