ythy / blog

Give everything a shot
7 stars 0 forks source link

Source Maps #44

Open ythy opened 6 years ago

ythy commented 6 years ago

One of the easiest performance wins you can gain for your website is to combine and compress your JavaScript and CSS files. But what happens when you need to debug the code within those compressed files? It can be a nightmare. Fear not however, there is a solution on the horizon and it goes by the name of source maps.

A source map provides a way of mapping code within a compressed file back to it’s original position in a source file. This means that – with the help of a bit of software – you can easily debug your applications even after your assets have been optimized.

When your source code has gone through transformations, debugging becomes a problem. When debugging in a browser, how to tell where the original code is? Source maps solve this problem by providing a mapping between the original and the transformed source code. In addition to source compiling to JavaScript, this works for styling as well.

How Source Maps Work

As the name suggests, a source map consists of a whole bunch of information that can be used to map the code within a compressed file back to it’s original source. You can specify a different source map for each of your compressed files.

You indicate to the browser that a source map is available by adding a special comment to the bottom of your optimised file.

//# sourceMappingURL=/path/to/script.js.map

You can also specify a source map is available by sending the X-SourceMap HTTP header in the response for the compressed JavaScript file. X-SourceMap: /path/to/script.js.map

The source map file contains a JSON object with information about the map itself and the original JavaScript files. Here is a simple example:

{
    version: 3,
    file: "script.js.map",
    sources: [
        "app.js",
        "content.js",
        "widget.js"
    ],
    sourceRoot: "/",
    names: ["slideUp", "slideDown", "save"],
    mappings: "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..."
}

Lets take a closer look at each of these properties.

Thanks to their speed, inline source maps are ideal for development. Given they make the bundles big, separate source maps are the preferred solution for production. Separate source maps work during development as well if the performance overhead is acceptable.

ythy commented 6 years ago

深入

One way to create a source map would be to simply store the source location (line, column) that corresponds to each output character’s location.

“feel the force” ⇒ Yoda ⇒ “the force feel”
Output location Input Input location Character
Line 1, Column 0 Yoda_input.txt Line 1, Column 5 t
Line 1, Column 1 Yoda_input.txt Line 1, Column 6 h
Line 1, Column 2 Yoda_input.txt Line 1, Column 7 e
Line 1, Column 4 Yoda_input.txt Line 1, Column 9 f

Mappings (283 chars length): 1|0|Yoda_input.txt|1|5, 1|1|Yoda_input.txt|1|6, 1|2|Yoda_input.txt|1|7, 1|4|Yoda_input.txt|1|9, 1|5|Yoda_input.txt|1|10, 1|6|Yoda_input.txt|1|11, 1|7|Yoda_input.txt|1|12, 1|8|Yoda_input.txt|1|13, 1|10|Yoda_input.txt|1|0, 1|11|Yoda_input.txt|1|1, 1|12|Yoda_input.txt|1|2, 1|13|Yoda_input.txt|1|3

1. Improvement: Don’t write output line number each time. Use ‘;’ to indicate a line change

Because in many cases the output is condensed into fewer lines than the input (minified JavaScript for example), not having to write the output line location for each entry results in great space savings. The same table we had before can now be encoded as follows:

Mappings (245 chars length): 0|Yoda_input.txt|1|5, 1|Yoda_input.txt|1|6, 2|Yoda_input.txt|1|7, 4|Yoda_input.txt|1|9, 5|Yoda_input.txt|1|10, 6|Yoda_input.txt|1|11, 7|Yoda_input.txt|1|12, 8|Yoda_input.txt|1|13, 10|Yoda_input.txt|1|0, 11|Yoda_input.txt|1|1, 12|Yoda_input.txt|1|2, 13|Yoda_input.txt|1|3;

2. Improvement: Table of names and inputs, referencing by indexes

Index Input
0 Yoda_input.txt
Index Name
0 the
1 force
2 feel
Output location Input index Input location Name index
Line 1, Column 0 0 Line 1, Column 5 0
Line 1, Column 4 0 Line 1, Column 9 1
Line 1, Column 10 0 Line 1, Column 0 2

Now this can be encoded as:

Inputs: Yoda_input.txt Names: the,force,feel Mappings (31 chars length): 0|0|1|5|0, 4|0|1|9|1, 10|0|1|0|2;

3. Improvement: Use relative (instead of absolute) offsets

The way we try to make most of the numbers smaller is by using relative offsets. Looking at the first column of the previous table, here’s what relative offsets mean:

Output location Input index Input location Name index
Line 1, Column 0 0 Line 1, Column 5 0
Line 1, Column +4 +0 Line 1, Column +4 +1
Line 1, Column +6 +0 Line 1, Column -9 +1

And this can now be encoded as:

Inputs: Yoda_input.txt Names: the,force,feel Mappings (31 chars length): 0|0|1|5|0, 4|0|1|4|1, 6|0|1|-9|1;

4. Improvement: VLQ (Variable Length Quantities)

VLQ in the decimal system

with VLQ all you have to do is add an indicator to digits to indicate that the number still has more digits. Let’s encode the following numbers using VLQ:

1|23|456|7

Using an underline as our indicator that a number has more digits (the continuation indicator):

12_34_5_67

Here’s how to read this:

Continuation binary group (5 bits for value): B5 B4 B3 B2 B1 B0 B5: continuation indicator B0 - B4: value

###### example
Encoding decimal 456:
The corresponding binary value takes up 9 bits (111001000), so it doesn’t fit in the first binary group and therefore requires a continuation. We will put the 4 least-significant bits in the first group (1000) and leave the rest (11100) for the continuation group.

B5 B4 B3 B2 B1 B0 B5 B4 B3 B2 B1 B0 1 1 0 0 0 0 0 1 1 1 0 0

###### The final binary result
Now all we have to do is concatenate the results we have so far:

000010 101110 000001 110000 011100 001110

###### Base64 encoding
As it turns out, traditional base64 encoding defines an alphabet of 64 characters. Values from 0 – 63 can be encoded as a single character. Well, the initial choice to use 6 bits per binary group was not arbitrary. By using 6 bits per group we can now encode each group as a single character as per the base 64 alphabet. Here’s what we get for the bits above:

CuBwcO


##### Applying VLQ + base64 to the Yoda example
>Inputs: Yoda_input.txt
Names: the,force,feel
Mappings (18 chars length): AACKA, IACIC, MACTC;

[参考](https://blogs.msdn.microsoft.com/davidni/2016/03/14/source-maps-under-the-hood-vlq-base64-and-yoda/)