What are JavaScript source maps?
Open the dev tools on this page,
and you’ll see it executes a strange source.jimscript
file.
It’s basically English, but you can step through and debug it:
But what on earth is JimScript,
and how does the browser know how to execute it?
Nowadays, when you use the browser dev tools to debug JavaScript,
it lies to you about what it’s actually running.
This is due to a feature called “source maps”.
Here’s a 5 minute intro to the web feature.
First, and rather insanely, the specification for this major browser feature is a random deleted Google Docs document.
So instead, we must learn by example.
When a browser loads a JavaScript file,
it looks for a source map for that file.
You can set this with an X-SourceMap
header,
or more commonly, with a comment in the JavaScript file itself, like this:
function main(x) {
const result = x*2;
console.log(result);
}
main(3);
That comment can go anywhere in the file,
but at the end is idiomatic.
The URL can be a relative URL,
which is interpreted relative to the URL of the JavaScript.
The idiom seems to be to keep the map URL next to the JavaScript URL,
e.g. the map for https://foo.com/scripts/main.js
is typically hosted at https://foo.com/scripts/main.js.map
.
The idiomatic file extension is .map
, but the browser doesn’t care.
The browser doesn’t even care about Content-Type
for this resource.
And the browser doesn’t respect CORS or anything;
you can set //# sourceMappingURL=https://google.com/
,
and the browser will happily load that page
and give you a syntax error when trying to parse it as a source map.
The source map is JSON format and will look something like this:
{
"version":3,
"sources":["source.jimscript"],
"names":["main"],
"mappings":"CAECA;CACAA;CACAA;;CAEA",
"file":"generated.js"
}
Clearly, the mappings
is the mysterious magic.
But I won’t go into the syntax here;
instead we just need to understand the content.
For that, we can use the source-map
library,
which exposes a parser and a generator for source maps.
This will read a source map and print a less mysterious representation:
const { readFileSync } = require('fs');
const { SourceMapConsumer } = require('source-map');
(async () => {
await SourceMapConsumer.with(
JSON.parse(readFileSync('./generated.js.map')),
null,
consumer => consumer.eachMapping(console.log)
);
})();
We get:
$ node parse.js
Mapping {
generatedLine: 1,
generatedColumn: 1,
lastGeneratedColumn: null,
source: 'source.jimscript',
originalLine: 3,
originalColumn: 1,
name: 'main'
}
Mapping {
generatedLine: 2,
generatedColumn: 1,
lastGeneratedColumn: null,
source: 'source.jimscript',
originalLine: 4,
originalColumn: 1,
name: 'main'
}
Mapping {
generatedLine: 3,
generatedColumn: 1,
lastGeneratedColumn: null,
source: 'source.jimscript',
originalLine: 5,
originalColumn: 1,
name: 'main'
}
Mapping {
generatedLine: 5,
generatedColumn: 1,
lastGeneratedColumn: null,
source: 'source.jimscript',
originalLine: 7,
originalColumn: 1,
name: null
}
So the source map is basically a series of statements of the form:
The characters starting at generatedLine
:generatedColumn
of the generated file
come from originalLine
:originalColumn
of the source source
,
where it had the name name
.
Importantly, the mappings are from the generated file back to the original source files.
They are not mappings from source files to generated files.
When you place a breakpoint in a source file using dev tools,
the browser has to try to reverse the mapping,
to find a reasonable breakpoint in the generated JavaScript.
But a given position in a source file might not have any equivalent position in a generated file.
Or it might have multiple positions in the generated file!
Note that each mapping only defines a starting index.
The ending index is implicitly defined by the start of the next mapping.
We learn from the source map that our file was generated from a source.jimscript
file
(at least in part; a generated file can have many sources).
That source file is referenced with a relative URL,
from which the browser downloads the source file.
(Alternatively, the source map can specify sourcesContent
,
which dumps the entire source into the source map file.
Either way, your entire source code is exposed.)
Lots of compilers generate source maps.
For example, TypeScript will generate a source map with:
$ npx tsc --sourceMap --outDir dist src/main.ts
Similar posts
How can I capture all crashes in a web page?
Including uncaught errors, unhandled promise rejections, image load failures, Content Security Policy violations, and console.error
calls. 2024-03-14
How do errors in a web page reach the dev console?
Errors in JavaScript cause an ErrorEvent
on window
. Preventing the default action blocks console output. Resource errors follow the capture-bubble event model. 2024-03-13
A formula for responsive font-size
Try setting the root font-size to calc(1rem + 0.25vw)
instead of using media queries with fixed breakpoints. 2024-03-12
Setting font-size based on viewing distance
Estimating viewing distance from screen size to set optimal font-size, but other factors like user mobility, long-sightedness, and short-sightedness complicate the ideal font-size calculation. 2024-03-11
How does HotJar record your screen?
“Session replay” tools record user interactions by capturing DOM changes, external resources, and viewport state, using efficient techniques like the MutationObserver API. 2024-03-09
How to replace GIF with HTML video in 2021
To replace a GIF with an HTML <video>
element in 2021, use a long list of attributes disable unwanted browser behaviors. 2021-01-19
More by Jim
What does the dot do in JavaScript?
foo.bar
, foo.bar()
, or foo.bar = baz
- what do they mean? A deep dive into prototypical inheritance and getters/setters. 2020-11-01
Smear phishing: a new Android vulnerability
Trick Android to display an SMS as coming from any contact. Convincing phishing vuln, but still unpatched. 2020-08-06
A probabilistic pub quiz for nerds
A “true or false” quiz where you respond with your confidence level, and the optimal strategy is to report your true belief. 2020-04-26
Time is running out to catch COVID-19
Simulation shows it’s rational to deliberately infect yourself with COVID-19 early on to get treatment, but after healthcare capacity is exceeded, it’s better to avoid infection. Includes interactive parameters and visualizations. 2020-03-14
The inception bar: a new phishing method
A new phishing technique that displays a fake URL bar in Chrome for mobile. A key innovation is the “scroll jail” that traps the user in a fake browser. 2019-04-27
The hacker hype cycle
I got started with simple web development, but because enamored with increasingly esoteric programming concepts, leading to a “trough of hipster technologies” before returning to more productive work. 2019-03-23
Project C-43: the lost origins of asymmetric crypto
Bob invents asymmetric cryptography by playing loud white noise to obscure Alice’s message, which he can cancel out but an eavesdropper cannot. This idea, published in 1944 by Walter Koenig Jr., is the forgotten origin of asymmetric crypto. 2019-02-16
How Hacker News stays interesting
Hacker News buried my post on conspiracy theories in my family due to overheated discussion, not censorship. Moderation keeps the site focused on interesting technical content. 2019-01-26
My parents are Flat-Earthers
For decades, my parents have been working up to Flat-Earther beliefs. From Egyptology to Jehovah’s Witnesses to theories that human built the Moon billions of years in the future. Surprisingly, it doesn’t affect their successful lives very much. For me, it’s a fun family pastime. 2019-01-20
The dots do matter: how to scam a Gmail user
Gmail’s “dots don’t matter” feature lets scammers create an account on, say, Netflix, with your email address but different dots. Results in convincing phishing emails. 2018-04-07
The sorry state of OpenSSL usability
OpenSSL’s inadequate documentation, confusing key formats, and deprecated interfaces make it difficult to use, despite its importance. 2017-12-02
I hate telephones
I hate telephones. Some rational reasons: lack of authentication, no spam filtering, forced synchronous communication. But also just a visceral fear. 2017-11-08
The Three Ts of Time, Thought and Typing: measuring cost on the web
Businesses often tout “free” services, but the real costs come in terms of time, thought, and typing required from users. Reducing these “Three Ts” is key to improving sign-up flows and increasing conversions. 2017-10-26
Granddad died today
Granddad died. The unspoken practice of death-by-dehydration in the NHS. The Liverpool Care Pathway. Assisted dying in the UK. The importance of planning in end-of-life care. 2017-05-19
How do I call a program in C, setting up standard pipes?
A C function to create a new process, set up its standard input/output/error pipes, and return a struct containing the process ID and pipe file descriptors. 2017-02-17
Your syntax highlighter is wrong
Syntax highlighters make value judgments about code. Most highlighters judge that comments are cruft, and try to hide them. Most diff viewers judge that code deletions are bad. 2014-05-11
Want to build a fantastic product using LLMs? I work at
Granola where we're building the future IDE for knowledge work. Come and work with us!
Read more or
get in touch! This page copyright James Fisher 2020. Content is not associated with my employer. Found an error? Edit this page.