@@ -148,10 +148,71 @@ let string_of_module_id
148148 Ext_namespace. js_name_of_modulename dep_module_id.id.name case suffix in
149149
150150 if Js_packages_info. same_package_by_name current_package_info dep_package_info then
151- Ext_path. node_rebase_file
152- ~from: cur_pkg.rel_path
153- ~to_: dep_pkg.rel_path
154- js_file
151+ (* Same-package imports: both files are in the same package.
152+
153+ Rewatch passes the full directory path via -bs-package-output, e.g.
154+ "/Users/barry/Projects/great-project/src/core/intl", so rel_path contains
155+ the actual directory.
156+
157+ BSB passes ninja's $in_d variable which expands per-file to the source directory.
158+ With the fix in bsb_package_specs.ml, this also contains the full source directory
159+ path, e.g. "/Users/barry/Projects/great-project/src/core/intl".
160+
161+ When both rel_path = ".":
162+ - Current file in src/core/Core_TempTests.res has rel_path = "."
163+ - Dependency in src/core/intl/Core_IntlTests.res has rel_path = "."
164+
165+ Calling node_rebase_file(".", ".", "Core_IntlTests.mjs") would incorrectly
166+ produce "./Core_IntlTests.mjs" when we need "./intl/Core_IntlTests.mjs".
167+
168+ To handle this, we extract the actual source directory from the dependency's
169+ .cmj file path.
170+ *)
171+ if cur_pkg.rel_path = " ." && dep_pkg.rel_path = " ." then
172+ (* Both rel_path are "." - extract actual source directories from .cmj locations.
173+
174+ In-source builds store .cmj files at lib/bs/<source_dir>/<module>.cmj
175+ Example: /Users/barry/Projects/great-project/lib/bs/src/core/intl/Core_IntlTests.cmj
176+
177+ We extract the source directory to calculate correct relative import paths. *)
178+ let cmj_file = dep_module_id.id.name ^ Literals. suffix_cmj in
179+ match Config_util. find_opt cmj_file with
180+ | Some cmj_path ->
181+ let cmj_dir = Filename. dirname cmj_path in
182+ let lib_bs_pattern = " /lib/bs/" in
183+ let source_dir =
184+ try
185+ (* Find "/lib/bs/" in the path and extract everything after it *)
186+ let idx = String. rindex_from cmj_dir (String. length cmj_dir - 1 ) '/' in
187+ let rec find_lib_bs pos =
188+ if pos < 0 then None
189+ else if Ext_string. starts_with (String. sub cmj_dir pos (String. length cmj_dir - pos)) lib_bs_pattern then
190+ Some (pos + String. length lib_bs_pattern)
191+ else
192+ find_lib_bs (pos - 1 )
193+ in
194+ match find_lib_bs idx with
195+ | Some start_idx ->
196+ (* Example: extract "src/core/intl" from ".../lib/bs/src/core/intl" *)
197+ String. sub cmj_dir start_idx (String. length cmj_dir - start_idx)
198+ | None -> cmj_dir
199+ with Not_found -> cmj_dir
200+ in
201+ Ext_path. node_rebase_file
202+ ~from: (Ext_path. absolute_cwd_path output_dir)
203+ ~to_: (Ext_path. absolute_cwd_path source_dir)
204+ (Filename. basename js_file)
205+ | None ->
206+ Ext_path. node_rebase_file
207+ ~from: cur_pkg.rel_path
208+ ~to_: dep_pkg.rel_path
209+ js_file
210+ else
211+ (* rel_path values contain directory information, use them directly *)
212+ Ext_path. node_rebase_file
213+ ~from: cur_pkg.rel_path
214+ ~to_: dep_pkg.rel_path
215+ js_file
155216 (* TODO: we assume that both [x] and [path] could only be relative path
156217 which is guaranteed by [-bs-package-output]
157218 *)
@@ -161,7 +222,56 @@ let string_of_module_id
161222 else
162223 begin match module_system with
163224 | Commonjs | Esmodule ->
164- dep_pkg.pkg_rel_path // js_file
225+ (* External package imports: importing from a different package.
226+
227+ When dep_pkg.rel_path = "." (dependency uses in-source builds),
228+ pkg_rel_path becomes "package_name/." (e.g., "a/."), which would
229+ generate invalid imports like "a/./A.res.js" instead of "a/src/A.res.js".
230+
231+ We extract the actual source directory from the dependency's .cmj file
232+ location and reconstruct the import path correctly.
233+ *)
234+ if dep_pkg.rel_path = " ." then
235+ let cmj_file = dep_module_id.id.name ^ Literals. suffix_cmj in
236+ match Config_util. find_opt cmj_file with
237+ | Some cmj_path ->
238+ (* External packages store .cmj at node_modules/<pkg>/lib/bs/<source_dir>/<module>.cmj
239+ Example: /Users/barry/Projects/great-project/node_modules/a/lib/bs/src/A-A.cmj
240+ We extract "src" from this path. *)
241+ let cmj_dir = Filename. dirname cmj_path in
242+ let lib_bs_pattern = " /lib/bs/" in
243+ let source_dir =
244+ try
245+ let idx = String. rindex_from cmj_dir (String. length cmj_dir - 1 ) '/' in
246+ let rec find_lib_bs pos =
247+ if pos < 0 then None
248+ else if Ext_string. starts_with (String. sub cmj_dir pos (String. length cmj_dir - pos)) lib_bs_pattern then
249+ Some (pos + String. length lib_bs_pattern)
250+ else
251+ find_lib_bs (pos - 1 )
252+ in
253+ match find_lib_bs idx with
254+ | Some start_idx ->
255+ String. sub cmj_dir start_idx (String. length cmj_dir - start_idx)
256+ | None -> " ."
257+ with Not_found -> " ."
258+ in
259+ (* Extract package name from pkg_rel_path: "a/." -> "a" *)
260+ let pkg_name =
261+ try
262+ let idx = String. rindex dep_pkg.pkg_rel_path '/' in
263+ String. sub dep_pkg.pkg_rel_path 0 idx
264+ with Not_found -> dep_pkg.pkg_rel_path
265+ in
266+ if source_dir = " ." then
267+ dep_pkg.pkg_rel_path // js_file
268+ else
269+ (* Reconstruct: "a" + "src" + "A.res.js" = "a/src/A.res.js" *)
270+ pkg_name // source_dir // js_file
271+ | None ->
272+ dep_pkg.pkg_rel_path // js_file
273+ else
274+ dep_pkg.pkg_rel_path // js_file
165275 (* Note we did a post-processing when working on Windows *)
166276 | Es6_global
167277 ->
0 commit comments