Skip to content

JS: jsconfig.json baseUrl not honoured — non-relative imports (static AND dynamic) produce no edge, orphaning every module in a Rails/webpacker app #2153

Description

@infusini

Summary

JS imports written against jsconfig.json's compilerOptions.baseUrl produce no edge. Relative specifiers in the same file resolve fine. This affects static and dynamic imports equally — it is not a dynamic-import() issue.

Graphify already parses jsconfig.json (it emits compilerOptions / baseUrl nodes), it just doesn't use it for module resolution.

Related but distinct: #147 asks for TypeScript tsconfig paths. This is plain JS + jsconfig baseUrl, which is the default layout for Rails/webpacker and Vue SPAs.

Reproduction

gfjs/
  jsconfig.json                       {"compilerOptions": {"baseUrl": "app/javascript"}}
  app/javascript/mods/Local.js        export default class Local {}
  app/javascript/mods/Widget.js       export default class Widget {}
  app/javascript/mods/Registrar.js
// Registrar.js
import Local  from './Local.js';        // relative
import Widget from 'mods/Widget.js';    // baseUrl-relative

export default class Registrar {
  static L = Local;
  static W = Widget;
  static async lazy() { return import('mods/Widget.js'); }
}

graphify extract . --code-only --force on 0.9.25:

Registrar.js     --imports_from --> Local.js
Registrar.js     --imports      --> Local
Registrar.js     --contains     --> Registrar
Registrar        --method       --> .lazy()
Widget.js        --contains     --> Widget      <-- no inbound edge at all
jsconfig.json    --contains     --> compilerOptions
compilerOptions  --contains     --> baseUrl     <-- config IS parsed
specifier edge created?
'./Local.js' relative
'mods/Widget.js' baseUrl static
import('mods/Widget.js') baseUrl dynamic

A separate matrix (no config) confirms relative works with and without the extension, so the variable is non-relative, not the extension.

Why it matters

In a real Rails + webpacker app (~17k nodes, jsconfig.json with baseUrl: app/javascript), every dashboard module and instruction is an orphan node. The registrar does:

// DashboardModuleRegistrar.js:58
import('workspaces/modules/registry/modules/EvosepSystemSuitabilityModule'),

and EvosepSystemSuitabilityModule.js has 0 inbound edges. So:

$ graphify affected "EvosepSystemSuitabilityModule.js" --depth 2
No affected nodes found.

…even though the registrar is a real dependency. On a "what will break if I change this?" question, a confident empty result is the worst possible failure mode — plain grep finds it in one call. This silently affects every module/instruction in the app.

Suggested fix

Honour jsconfig.json / tsconfig.json compilerOptions.baseUrl (and paths) when resolving JS specifiers, same as #147 proposes for TS. Falling back to "try <baseUrl>/<specifier>" would cover the common case.

Environment

graphifyy 0.9.25 · macOS 15 (darwin 25.5.0) · Python 3.9 · --code-only

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions