Skip to content

Commit ffd1ddb

Browse files
Sourcemap tab
1 parent ebf3f19 commit ffd1ddb

12 files changed

Lines changed: 799 additions & 85 deletions

File tree

compiler/core/js_source_map.ml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type t = {
4141
generated_dir: string;
4242
source_root: string;
4343
sources_content: bool;
44+
provided_source_contents: (string, string) Hashtbl.t;
4445
sources: (string, int) Hashtbl.t;
4546
mutable source_list: source list;
4647
mutable mappings: mapping list;
@@ -109,20 +110,27 @@ let relative_path ~from_dir ~to_file =
109110
let parts = repeat ".." (List.length from_rest) @ to_rest in
110111
if parts = [] then Filename.basename to_file else String.concat "/" parts
111112

112-
let make ~generated_file ~source_root ~sources_content =
113+
let make ~source_contents ~generated_file ~source_root ~sources_content =
114+
let provided_source_contents = Hashtbl.create (List.length source_contents) in
115+
source_contents
116+
|> List.iter (fun (filename, content) ->
117+
Hashtbl.replace provided_source_contents (absolute_path filename) content);
113118
{
114119
generated_file = Filename.basename generated_file;
115120
generated_dir = Filename.dirname generated_file;
116121
source_root;
117122
sources_content;
123+
provided_source_contents;
118124
sources = Hashtbl.create 4;
119125
source_list = [];
120126
mappings = [];
121127
last_generated = None;
122128
}
123129

124-
let load_content filename =
125-
try Some (Ext_io.load_file filename) with _ -> None
130+
let load_content builder filename =
131+
match Hashtbl.find_opt builder.provided_source_contents filename with
132+
| Some content -> Some content
133+
| None -> (try Some (Ext_io.load_file filename) with _ -> None)
126134

127135
let add_source builder filename =
128136
let filename =
@@ -138,7 +146,7 @@ let add_source builder filename =
138146
{
139147
relative_path =
140148
relative_path ~from_dir:builder.generated_dir ~to_file:filename;
141-
content = load_content filename;
149+
content = load_content builder filename;
142150
}
143151
in
144152
let index = List.length builder.source_list in

compiler/core/js_source_map.mli

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
type t
22

33
val make :
4-
generated_file:string -> source_root:string -> sources_content:bool -> t
4+
source_contents:(string * string) list ->
5+
generated_file:string ->
6+
source_root:string ->
7+
sources_content:bool ->
8+
t
59

610
val with_builder : t -> (unit -> 'a) -> 'a
711

compiler/core/lam_compile_main.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ let remove_stale_source_map ?(remove_stale_map = true) target_file =
434434
let dump_deps_program_with_source_map ?(remove_stale_map = true) ~target_file
435435
~output_prefix module_system lambda_output chan =
436436
let builder =
437-
Js_source_map.make ~generated_file:target_file
437+
Js_source_map.make ~source_contents:[] ~generated_file:target_file
438438
~source_root:!Js_config.source_map_root
439439
~sources_content:!Js_config.source_map_sources_content
440440
in

compiler/jsoo/jsoo_playground_main.ml

Lines changed: 122 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
* v5: Removed .ml support.
5353
* v6: Added `config.experimental_features` and `config.jsx_preserve_mode` to the BundleConfig.
5454
* v7: Added debug dump output APIs for developer playground tooling,
55-
* including gentype output.
55+
* including gentype and source map output.
5656
* *)
5757
let api_version = "7"
5858

@@ -80,6 +80,9 @@ module Bundle_config = struct
8080
mutable experimental_features: string list;
8181
mutable jsx_preserve_mode: bool;
8282
mutable gentype_enabled: bool;
83+
mutable source_map_mode: Js_config.source_map;
84+
mutable source_map_sources_content: bool;
85+
mutable source_map_root: string;
8386
}
8487

8588
let make () =
@@ -91,6 +94,9 @@ module Bundle_config = struct
9194
experimental_features = [];
9295
jsx_preserve_mode = false;
9396
gentype_enabled = false;
97+
source_map_mode = No_source_map;
98+
source_map_sources_content = false;
99+
source_map_root = "";
94100
}
95101

96102
let default_filename (lang : Lang.t) = "playground." ^ Lang.to_string lang
@@ -99,6 +105,21 @@ module Bundle_config = struct
99105
match m with
100106
| Ext_module_system.Commonjs -> "commonjs"
101107
| Esmodule -> "esmodule"
108+
109+
let source_map_of_string value =
110+
match String.lowercase_ascii value with
111+
| "linked" -> Some Js_config.Linked
112+
| "inline" -> Some Inline
113+
| "hidden" -> Some Hidden
114+
| "false" | "none" | "disabled" -> Some No_source_map
115+
| _ -> None
116+
117+
let string_of_source_map mode =
118+
match mode with
119+
| Js_config.Linked -> "linked"
120+
| Inline -> "inline"
121+
| Hidden -> "hidden"
122+
| No_source_map -> "false"
102123
end
103124

104125
type loc_err_info = {
@@ -542,6 +563,47 @@ module Compile = struct
542563
^ "\n" ^ code_text ^ "\n"
543564
else "No @gentype annotations found."
544565

566+
let render_javascript ~module_system ~filename ~source ~source_map_mode
567+
~source_map_sources_content ~source_map_root lambda_output =
568+
let buffer = Buffer.create 1000 in
569+
let generated_file =
570+
Filename.concat (Sys.getcwd ())
571+
(Filename.remove_extension (Filename.basename filename)
572+
^ Literals.suffix_js)
573+
in
574+
let source_map_builder =
575+
match source_map_mode with
576+
| Js_config.No_source_map -> None
577+
| Linked | Inline | Hidden ->
578+
Some
579+
(Js_source_map.make ~source_contents:[(filename, source)] ~generated_file
580+
~source_root:source_map_root
581+
~sources_content:source_map_sources_content)
582+
in
583+
let print_javascript () =
584+
Js_dump_program.pp_deps_program ~output_prefix:"" module_system
585+
lambda_output (Ext_pp.from_buffer buffer)
586+
in
587+
(match source_map_builder with
588+
| None -> print_javascript ()
589+
| Some builder -> Js_source_map.with_builder builder print_javascript);
590+
let source_map =
591+
match source_map_builder with
592+
| None -> None
593+
| Some builder ->
594+
let json = Js_source_map.json builder in
595+
(match source_map_mode with
596+
| Linked ->
597+
Buffer.add_string buffer
598+
(Js_source_map.linked_comment ~map_file:(generated_file ^ ".map"))
599+
| Inline ->
600+
Buffer.add_string buffer (Js_source_map.inline_comment ~json)
601+
| Hidden -> ()
602+
| No_source_map -> assert false);
603+
Some json
604+
in
605+
(Buffer.contents buffer, source_map)
606+
545607
let implementation ?(include_debug_outputs = false)
546608
~(config : Bundle_config.t) ~lang str =
547609
let {
@@ -551,6 +613,9 @@ module Compile = struct
551613
experimental_features;
552614
jsx_preserve_mode;
553615
gentype_enabled;
616+
source_map_mode;
617+
source_map_sources_content;
618+
source_map_root;
554619
} =
555620
config
556621
in
@@ -572,6 +637,9 @@ module Compile = struct
572637
let types_signature = ref [] in
573638
Js_config.jsx_version := Some Js_config.Jsx_v4;
574639
Js_config.jsx_preserve := jsx_preserve_mode;
640+
Js_config.source_map := source_map_mode;
641+
Js_config.source_map_sources_content := source_map_sources_content;
642+
Js_config.source_map_root := source_map_root;
575643
experimental_features
576644
|> List.iter Experimental_features.enable_from_string;
577645
(* default *)
@@ -589,19 +657,18 @@ module Compile = struct
589657
let {Translmod.lambda; exports; hoisted_functions} =
590658
Translmod.transl_implementation modulename typed_tree
591659
in
592-
let buffer = Buffer.create 1000 in
593-
let () =
594-
Js_dump_program.pp_deps_program ~output_prefix:""
595-
(* does not matter here *) module_system
596-
(Lam_compile_main.compile "" exports hoisted_functions lambda)
597-
(Ext_pp.from_buffer buffer)
660+
let lambda_output =
661+
Lam_compile_main.compile "" exports hoisted_functions lambda
662+
in
663+
let js_code, source_map =
664+
render_javascript ~module_system ~filename ~source:str ~source_map_mode
665+
~source_map_sources_content ~source_map_root lambda_output
598666
in
599-
let v = Buffer.contents buffer in
600667
let type_hints = collect_type_hints typed_tree in
601668
let attrs =
602669
Js.Unsafe.
603670
[|
604-
("js_code", inject @@ Js.string v);
671+
("js_code", inject @@ Js.string js_code);
605672
( "warnings",
606673
inject
607674
@@ (!warning_infos
@@ -633,13 +700,12 @@ module Compile = struct
633700
|]
634701
else [||]
635702
in
636-
(* TODO(sourcemap PR): The developer playground already knows how to
637-
show an optional "source_map" debug tab. Add optional instance
638-
setters named "setSourceMapMode", "setSourceMapSourcesContent",
639-
and "setSourceMapRoot", matching getConfig fields named
640-
"source_map_mode", "source_map_sources_content", and
641-
"source_map_root", plus a compileWithDebug output field named
642-
"source_map". *)
703+
let source_map_attrs =
704+
match source_map with
705+
| None -> [||]
706+
| Some source_map ->
707+
Js.Unsafe.[|("source_map", inject @@ Js.string source_map)|]
708+
in
643709
let debug_attrs =
644710
Js.Unsafe.
645711
[|
@@ -649,7 +715,8 @@ module Compile = struct
649715
("lam", inject @@ Js.string lam);
650716
|]
651717
in
652-
Js.Unsafe.obj (Array.concat [attrs; debug_attrs; gentype_attrs])
718+
Js.Unsafe.obj
719+
(Array.concat [attrs; debug_attrs; gentype_attrs; source_map_attrs])
653720
else Js.Unsafe.obj attrs
654721
with e -> (
655722
match e with
@@ -754,6 +821,21 @@ module Export = struct
754821
config.gentype_enabled <- value;
755822
true
756823
in
824+
let set_source_map_mode value =
825+
match Bundle_config.source_map_of_string value with
826+
| Some source_map_mode ->
827+
config.source_map_mode <- source_map_mode;
828+
true
829+
| None -> false
830+
in
831+
let set_source_map_sources_content value =
832+
config.source_map_sources_content <- value;
833+
true
834+
in
835+
let set_source_map_root value =
836+
config.source_map_root <- value;
837+
true
838+
in
757839
let convert_syntax ~(from_lang : string) ~(to_lang : string) (src : string)
758840
=
759841
let open Lang in
@@ -816,6 +898,19 @@ module Export = struct
816898
inject
817899
@@ Js.wrap_meth_callback (fun _ value ->
818900
Js.bool (set_gentype_enabled (Js.to_bool value))) );
901+
( "setSourceMapMode",
902+
inject
903+
@@ Js.wrap_meth_callback (fun _ value ->
904+
Js.bool (set_source_map_mode (Js.to_string value))) );
905+
( "setSourceMapSourcesContent",
906+
inject
907+
@@ Js.wrap_meth_callback (fun _ value ->
908+
Js.bool
909+
(set_source_map_sources_content (Js.to_bool value))) );
910+
( "setSourceMapRoot",
911+
inject
912+
@@ Js.wrap_meth_callback (fun _ value ->
913+
Js.bool (set_source_map_root (Js.to_string value))) );
819914
( "getConfig",
820915
inject
821916
@@ Js.wrap_meth_callback (fun _ ->
@@ -832,6 +927,16 @@ module Export = struct
832927
inject @@ (config.jsx_preserve_mode |> Js.bool) );
833928
( "gentype_enabled",
834929
inject @@ (config.gentype_enabled |> Js.bool) );
930+
( "source_map_mode",
931+
inject
932+
@@ (config.source_map_mode
933+
|> Bundle_config.string_of_source_map |> Js.string)
934+
);
935+
( "source_map_sources_content",
936+
inject
937+
@@ (config.source_map_sources_content |> Js.bool) );
938+
( "source_map_root",
939+
inject @@ Js.string config.source_map_root );
835940
( "experimental_features",
836941
inject
837942
@@ (config.experimental_features |> Array.of_list

packages/dev-playground/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
"prepare-pages-site": "node scripts/prepare-pages-site.mjs",
1010
"res:build": "rescript",
1111
"res:watch": "rescript -w",
12+
"test": "rescript && node --test scripts/source-map-navigation.test.mjs",
1213
"dev": "vite --host 127.0.0.1",
1314
"build": "rescript && vite build",
1415
"preview": "vite preview --host 127.0.0.1"
1516
},
1617
"dependencies": {
18+
"@jridgewell/trace-mapping": "^0.3.31",
1719
"@rescript/runtime": "12.3.0",
1820
"rescript": "12.3.0",
1921
"vite": "^8.0.14",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
4+
import {
5+
decode,
6+
generatedForOriginal,
7+
} from "../src/SourceMapNavigation.res.mjs";
8+
9+
const sourceMap = JSON.stringify({
10+
version: 3,
11+
file: "Playground.js",
12+
sources: ["Playground.res"],
13+
names: [],
14+
mappings: "AAAA;AACA",
15+
});
16+
17+
test("decodes source map positions", () => {
18+
assert.deepEqual(decode(sourceMap), [
19+
{
20+
generated: { line: 1, col: 0 },
21+
original: {
22+
source: "Playground.res",
23+
position: { line: 1, col: 0 },
24+
},
25+
},
26+
{
27+
generated: { line: 2, col: 0 },
28+
original: {
29+
source: "Playground.res",
30+
position: { line: 2, col: 0 },
31+
},
32+
},
33+
]);
34+
});
35+
36+
test("finds the closest generated position for a source position", () => {
37+
const mapping = generatedForOriginal(decode(sourceMap), { line: 2, col: 5 });
38+
39+
assert.deepEqual(mapping?.generated, { line: 2, col: 0 });
40+
});
41+
42+
test("does not carry a generated position onto an unmapped source line", () => {
43+
const mapping = generatedForOriginal(decode(sourceMap), { line: 3, col: 0 });
44+
45+
assert.equal(mapping, undefined);
46+
});
47+
48+
test("handles invalid source maps", () => {
49+
assert.deepEqual(decode("not a source map"), []);
50+
});

0 commit comments

Comments
 (0)