Using original JS source maps

shadow-cljs allows using arbitrary JS files as documented in 12.2.3. JavaScript Dialects. For source maps, shadow-cljs appears to create new ones from the generated (and included) JS files. Is it possible to pass the source maps from the original compilation (e.g. babel or other tool) through to the shadow-cljs output? This could be related to this GitHub issue, but I’m not quite sure if this is the same use case. Also this issue is quite old (2017), perhaps there’s another way to solve this. Thanks!

Unfortunately this is not working properly. I haven’t figured out how to get it to work reliably. It is supposed to work automatically assuming the generated JS files have either inlined source maps or the source maps available in the same directory and properly linked via sourceMappingUrl. It does however not seem to work for JS files, not sure why. It works fine for CLJS files.

Thanks Thomas! Could this be path related? If I include

//# sourceMappingURL=src/gen/example.js.map

it does try and look for the file. If for example I give an illegal path like src/gen/idontexist.js.map an error is given on recompilation:

Failed to resolve sourcemap at src/gen/idontexist.js.map svelte.js.map: src/gen/idontexist.js.map

which appears to be an error coming from the Google Closure compiler. Note that generated js and map file are both in src/gen. So I’d actually expect adding just //# sourceMappingURL=example.js.map to the generated js file (without the src/gen path).

edit: formatting.

I think I found a way to solve this; very much by trial and error and works in Chrome and Firefox (not in Safari for unknown reasons). Slight modification to the function convert-sources* in source file shadow.build.closure.clj is needed (used version 2.11.1). I’ve added the following settings to the compiler options co:

(.setApplyInputSourceMaps co true)
(.setSourceMapIncludeSourcesContent co true)

This part of the code needed to be removed to have the original source map contents included:

(.reset source-map)

And this part doesn’t seem to do anything anymore (and could be removed?)

;; for sourcesContent
(.addSourceFile (.getName source-file) (.getCode source-file))

I have tested this with transpiled JS sources which include source map with original file contents (a comment added at the end of the generated JS file, e.g. //# sourceMappingURL=data:application/json;base64,ewogI...) which appears to be working well.

Unfortunately I’m not intimately familiar with the Google Closure compiler, so it’s very hard for me to tell if these are proper changes and don’t break existing stuff. If you’d like, I’ll gladly make a PR for this to review / discuss.

Thanks!

(.reset source-map) must be called. Otherwise it’ll fail when trying to generate the source map for another file.

You might be onto something though since I already re-add the sources when generating source maps for optimized sources. That might be all thats missing.

Hmm no luck. Can’t get Chrome to display the actual sources. Can you share the source mapped file you tried this with? Maybe I’m doing something wrong when generating it.

/* example.svelte generated by Svelte v3.24.1 */
import {
	SvelteComponentDev,
	add_location,
	append_dev,
	attr_dev,
	detach_dev,
	dispatch_dev,
	element,
	globals,
	init,
	insert_dev,
	listen_dev,
	noop,
	safe_not_equal,
	set_data_dev,
	space,
	text,
	validate_slots
} from "svelte/internal";

const { Error: Error_1 } = globals;
import { getmessage } from "goog:cljsvelte.utils";
const file = "example.svelte";

function add_css() {
	var style = element("style");
	style.id = "svelte-klh1a0-style";
	style.textContent = ".btn.svelte-klh1a0{color:cadetblue}\n/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5zdmVsdGUiLCJzb3VyY2VzIjpbImV4YW1wbGUuc3ZlbHRlIl0sInNvdXJjZXNDb250ZW50IjpbIjxzY3JpcHQ+XG4gIGltcG9ydCB7IGdldG1lc3NhZ2UgfSBmcm9tIFwiZ29vZzpjbGpzdmVsdGUudXRpbHNcIjtcbiAgZXhwb3J0IGxldCBjb3VudDtcblxuICAkOiBpZiAoY291bnQgPj0gMTApIHtcbiAgICBhbGVydChgY291bnQgaXMgZGFuZ2Vyb3VzbHkgaGlnaCFgKTtcbiAgICBjb3VudCA9IDk7XG4gIH1cblxuICBmdW5jdGlvbiBoYW5kbGVDbGljaygpIHtcbiAgICBjb3VudCArPSAxO1xuICAgIHRocm93IEVycm9yKFwiS2Fib29tXCIpO1xuICB9XG48L3NjcmlwdD5cblxuPHN0eWxlPlxuICAuYnRuIHtcbiAgICBjb2xvcjogY2FkZXRibHVlO1xuICB9XG48L3N0eWxlPlxuXG48YnV0dG9uIGNsYXNzPVwiYnRuXCIgb246Y2xpY2s9e2hhbmRsZUNsaWNrfT5cbiAgQ2xpY2tlZCB7Y291bnR9IHtjb3VudCA9PT0gMSA/ICd0aW1lJyA6ICd0aW1lcyd9IHtnZXRtZXNzYWdlKCl9XG48L2J1dHRvbj5cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFnQkUsSUFBSSxjQUFDLENBQUMsQUFDSixLQUFLLENBQUUsU0FBUyxBQUNsQixDQUFDIn0= */";
	append_dev(document.head, style);
}

function create_fragment(ctx) {
	let button;
	let t0;
	let t1;
	let t2;
	let t3_value = (/*count*/ ctx[0] === 1 ? "time" : "times") + "";
	let t3;
	let t4;
	let t5_value = getmessage() + "";
	let t5;
	let mounted;
	let dispose;

	const block = {
		c: function create() {
			button = element("button");
			t0 = text("Clicked ");
			t1 = text(/*count*/ ctx[0]);
			t2 = space();
			t3 = text(t3_value);
			t4 = space();
			t5 = text(t5_value);
			attr_dev(button, "class", "btn svelte-klh1a0");
			add_location(button, file, 21, 0, 306);
		},
		l: function claim(nodes) {
			throw new Error_1("options.hydrate only works if the component was compiled with the `hydratable: true` option");
		},
		m: function mount(target, anchor) {
			insert_dev(target, button, anchor);
			append_dev(button, t0);
			append_dev(button, t1);
			append_dev(button, t2);
			append_dev(button, t3);
			append_dev(button, t4);
			append_dev(button, t5);

			if (!mounted) {
				dispose = listen_dev(button, "click", /*handleClick*/ ctx[1], false, false, false);
				mounted = true;
			}
		},
		p: function update(ctx, [dirty]) {
			if (dirty & /*count*/ 1) set_data_dev(t1, /*count*/ ctx[0]);
			if (dirty & /*count*/ 1 && t3_value !== (t3_value = (/*count*/ ctx[0] === 1 ? "time" : "times") + "")) set_data_dev(t3, t3_value);
		},
		i: noop,
		o: noop,
		d: function destroy(detaching) {
			if (detaching) detach_dev(button);
			mounted = false;
			dispose();
		}
	};

	dispatch_dev("SvelteRegisterBlock", {
		block,
		id: create_fragment.name,
		type: "component",
		source: "",
		ctx
	});

	return block;
}

function instance($$self, $$props, $$invalidate) {
	let { count } = $$props;

	function handleClick() {
		$$invalidate(0, count += 1);
		throw Error("Kaboom");
	}

	const writable_props = ["count"];

	Object.keys($$props).forEach(key => {
		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Example> was created with unknown prop '${key}'`);
	});

	let { $$slots = {}, $$scope } = $$props;
	validate_slots("Example", $$slots, []);

	$$self.$$set = $$props => {
		if ("count" in $$props) $$invalidate(0, count = $$props.count);
	};

	$$self.$capture_state = () => ({ getmessage, count, handleClick });

	$$self.$inject_state = $$props => {
		if ("count" in $$props) $$invalidate(0, count = $$props.count);
	};

	if ($$props && "$$inject" in $$props) {
		$$self.$inject_state($$props.$$inject);
	}

	$$self.$$.update = () => {
		if ($$self.$$.dirty & /*count*/ 1) {
			$: if (count >= 10) {
				alert(`count is dangerously high!`);
				$$invalidate(0, count = 9);
			}
		}
	};

	return [count, handleClick];
}

class Example extends SvelteComponentDev {
	constructor(options) {
		super(options);
		if (!document.getElementById("svelte-klh1a0-style")) add_css();
		init(this, options, instance, create_fragment, safe_not_equal, { count: 0 });

		dispatch_dev("SvelteRegisterComponent", {
			component: this,
			tagName: "Example",
			options,
			id: create_fragment.name
		});

		const { ctx } = this.$$;
		const props = options.props || {};

		if (/*count*/ ctx[0] === undefined && !("count" in props)) {
			console.warn("<Example> was created without expected prop 'count'");
		}
	}

	get count() {
		throw new Error_1("<Example>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
	}

	set count(value) {
		throw new Error_1("<Example>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
	}
}

export default Example;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiZXhhbXBsZS5zdmVsdGUiXSwic291cmNlc0NvbnRlbnQiOlsiPHNjcmlwdD5cbiAgaW1wb3J0IHsgZ2V0bWVzc2FnZSB9IGZyb20gXCJnb29nOmNsanN2ZWx0ZS51dGlsc1wiO1xuICBleHBvcnQgbGV0IGNvdW50O1xuXG4gICQ6IGlmIChjb3VudCA+PSAxMCkge1xuICAgIGFsZXJ0KGBjb3VudCBpcyBkYW5nZXJvdXNseSBoaWdoIWApO1xuICAgIGNvdW50ID0gOTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGhhbmRsZUNsaWNrKCkge1xuICAgIGNvdW50ICs9IDE7XG4gICAgdGhyb3cgRXJyb3IoXCJLYWJvb21cIik7XG4gIH1cbjwvc2NyaXB0PlxuXG48c3R5bGU+XG4gIC5idG4ge1xuICAgIGNvbG9yOiBjYWRldGJsdWU7XG4gIH1cbjwvc3R5bGU+XG5cbjxidXR0b24gY2xhc3M9XCJidG5cIiBvbjpjbGljaz17aGFuZGxlQ2xpY2t9PlxuICBDbGlja2VkIHtjb3VudH0ge2NvdW50ID09PSAxID8gJ3RpbWUnIDogJ3RpbWVzJ30ge2dldG1lc3NhZ2UoKX1cbjwvYnV0dG9uPlxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O1NBQ1csVUFBVSxRQUFRLHNCQUFzQjs7Ozs7Ozs7Ozs7Ozs7OzJCQXFCaEMsR0FBSyxRQUFLLENBQUMsR0FBRyxNQUFNLEdBQUcsT0FBTzs7O2dCQUFHLFVBQVU7Ozs7Ozs7Ozt1QkFBbkQsR0FBSzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzBEQURjLEdBQVc7Ozs7O3VEQUM5QixHQUFLO2tFQUFHLEdBQUssUUFBSyxDQUFDLEdBQUcsTUFBTSxHQUFHLE9BQU87Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O09BcEJwQyxLQUFLOztVQU9QLFdBQVc7a0JBQ2xCLEtBQUssSUFBSSxDQUFDO1FBQ0osS0FBSyxDQUFDLFFBQVE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FQdEIsQ0FBQyxNQUFNLEtBQUssSUFBSSxFQUFFO0lBQ2hCLEtBQUs7b0JBQ0wsS0FBSyxHQUFHLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OyIsImZpbGUiOiJtb2R1bGUkZXhhbXBsZV9zdmVsdGUuanMifQ==

whipped up a quick repo, see GitHub. Runs with npm run dev.

Just pushed a rather hacky attempt to fix this. Needs more testing but it appears to work. I’ll probably make a release with this soon. Or you can just take the closure.clj in master and try it directly, like you have in that repo.

1 Like

I’m getting some unexpected behavior in terms of mapping alignment. i.e., positions in source file is not quite where I’d expect them (dev, haven’t looked at prod yet). I’ll investigate more into this, could very well be related to my project setup and not related to GCC/Shadow. Other than that, seems to work!