• Stars
    star
    3,830
  • Rank 11,482 (Top 0.3 %)
  • Language
    TypeScript
  • License
    Apache License 2.0
  • Created about 9 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Analyze and debug space usage through source maps

Build Status NPM version install size Coverage Status NPM

source-map-explorer

Analyze and debug JavaScript (or Sass or LESS) code bloat through source maps.

The source map explorer determines which file each byte in your minified code came from. It shows you a treemap visualization to help you debug where all the code is coming from. Check out this Chrome Developer video (3:25) for a demo of the tool in action.

Install:

npm install -g source-map-explorer

Use (you can specify filenames or use glob pattern):

source-map-explorer bundle.min.js
source-map-explorer bundle.min.js bundle.min.js.map
source-map-explorer bundle.min.js*
source-map-explorer *.js

This will open up a visualization of how the space is used in your minified bundle:

Here's a demo with a more complex bundle.

Here's another demo where you can see a bug: there are two copies of React in the bundle (perhaps because of out-of-date dependencies).

Requirements

  • Node 10 or later

Options

Default behavior - write HTML to a temp file and open it in your browser

source-map-explorer foo.min.js

Write output in specific formats to stdout

source-map-explorer foo.min.js --html
source-map-explorer foo.min.js --json
source-map-explorer foo.min.js --tsv

Write output in specific formats to a file

source-map-explorer foo.min.js --html result.html
source-map-explorer foo.min.js --json result.json
source-map-explorer foo.min.js --tsv result.tsv
  • --json: output JSON instead of displaying a visualization:

    source-map-explorer foo.min.js --json
    {
      "results": [
        {
          "bundleName": "tests/data/foo.min.js",
          "totalBytes": 718,
          "mappedBytes": 681,
          "unmappedBytes": 1,
          "eolBytes": 1,
          "sourceMapCommentBytes": 35,
          "files": {
            "node_modules/browser-pack/_prelude.js": {
              "size": 480
            },
            "src/bar.js": {
              "size": 104
            },
            "src/foo.js": {
              "size": 97
            },
            "[sourceMappingURL]": {
              "size": 35
            },
            "[unmapped]": {
              "size": 1
            },
            "[EOLs]": {
              "size": 1
            }
          }
        }
      ]
    }
    
  • --tsv: output tab-delimited values instead of displaying a visualization:

    source-map-explorer foo.min.js --tsv
    Source                                  Size
    node_modules/browser-pack/_prelude.js   480
    src/bar.js                              104
    src/foo.js                              97
    [sourceMappingURL]                      35
    [unmapped]                              1
    [EOLs]                                  1
    

    If you just want a list of files, you can do source-map-explorer foo.min.js --tsv | sed 1d | cut -f1.

  • --html: output HTML to stdout. If you want to save the output (e.g. to share), specify filename after --html:

    source-map-explorer foo.min.js --html tree.html
    
  • -m, --only-mapped: exclude "unmapped" bytes from the output. This will result in total counts less than the file size.

  • --exclude-source-map: exclude source map comment size from output. This will result in total counts less than the file size.

  • --replace, --with: The paths in source maps sometimes have artifacts that are difficult to get rid of. These flags let you do simple find & replaces on the paths. For example:

    source-map-explorer foo.min.js --replace 'dist/' --with ''
    

    You can specify these flags multiple times. Be aware that the find/replace is done after eliminating shared prefixes between paths.

    These are regular expressions.

  • --no-root: By default, source-map-explorer finds common prefixes between all source files and eliminates them, since they add complexity to the visualization with no real benefit. But if you want to disable this behavior, set the --no-root flag.

  • --no-border-checks: Disable invalid mapping column/line checks. By default, when a source map references column/line with bigger index than available in the source source-map-explorers throws an error indicating that specified source map might be wrong for the source.

  • --coverage: If the path to a valid a chrome code coverage JSON export is supplied, the tree map will be colorized according to which percentage of the modules code was executed

  • --gzip: Calculate gzip size. It also sets onlyMapped flag

  • --sort: Sort filenames

Examples

Get help

source-map-explorer -h
Analyze and debug space usage through source maps.
Usage:
source-map-explorer script.js [script.js.map] [--json [result.json] | --html [result.html] | --tsv [result.csv]] [-m | --only-mapped] [--exclude-source-map] [--no-border-checks] [--gzip] [--sort] [--replace=BEFORE_1 BEFORE_2 --with=AFTER_1 AFTER_2] [--no-root] [--coverage coverage.json] [--version] [--help | -h]

Output:
  --json  If filename specified save output as JSON to specified file otherwise output to stdout.  [string]
  --tsv   If filename specified save output as TSV to specified file otherwise output to stdout.  [string]
  --html  If filename specified save output as HTML to specified file otherwise output to stdout rather than opening a browser.  [string]

Replace:
  --replace  Apply a simple find/replace on source file names. This can be used to fix some oddities with paths that appear in the source map generation process. Accepts regular expressions.
  [array]
  --with     See --replace.  [array]

Options:
  --version             Show version number  [boolean]
  --only-mapped, -m     Exclude "unmapped" bytes from the output. This will result in total counts less than the file size  [boolean]
  --exclude-source-map  Exclude source map comment size from output  [boolean]
  --no-root             To simplify the visualization, source-map-explorer will remove any prefix shared by all sources. If you wish to disable this behavior, set --no-root.  [boolean]
  --no-border-checks    Disable invalid mapping column/line checks.  [boolean]
  --coverage            If the path to a valid a chrome code coverage JSON export is supplied, the tree map will be colorized according to which percentage of the modules code was executed
[string]
  --gzip                Calculate gzip size. It also sets onlyMapped flag  [boolean]
  --sort                Sort filenames  [boolean]
  -h, --help            Show help  [boolean]

Examples:
  source-map-explorer script.js script.js.map       Explore bundle
  source-map-explorer script.js                     Explore bundle with inline source map
  source-map-explorer dist/js/*.*                   Explore all bundles inside dist/js folder
  source-map-explorer script.js --tsv               Explore and output result as TSV to stdout
  source-map-explorer script.js --json result.json  Explore and save result as JSON to the file

Explore bundle and view result as an interactive map

source-map-explorer script.js script.js.map

Will open an HTML file containing explore result as a tree data map

Result as HTML tree data map

Explore and output result as JSON

source-map-explorer script.js script.js.map --json
{
  "results": [
    {
      "bundleName": "script.js",
      "totalBytes": 718,
      "unmappedBytes": 1,
      "eolBytes": 1,
      "sourceMapCommentBytes": 35,
      "files": {
        "node_modules/browser-pack/_prelude.js": {
          "size": 480
        }
        "src/bar.js": {
          "size": 104
        }
        "src/foo.js": {
          "size": 97
        },
        "[sourceMappingURL]": {
          "size": 35
        }
        "[unmapped]": {
          "size": 1
        }
      }
    }
  ]
}

Explore and output result as TSV

source-map-explorer script1.js script2.js --tsv
Source  Size
node_modules/browser-pack/_prelude.js   480
src/bar.js      104
src/foo.js      97
[sourceMappingURL]      35
[unmapped]      1

[sourceMappingURL]      2308
node_modules/browser-pack/_prelude.js   480
src/bar.js      104
src/foo.js      97
[unmapped]      1

Explore and output result as HTML

source-map-explorer script.js --html
<!doctype html>
<html lang="en">
<head>
...
  selectBundle(selectedBundle);
</script>
<html>

Explore and save result as HTML file

source-map-explorer script.js --html ./sme/result.html

Replace substring in filenames

source-map-explorer script.js --tsv --replace dist node_modules --with gist modules
Source  Size
gist/bar.js     2854
modules/browserify/modules/browser-pack/_prelude.js     463
gist/foo.js     137
[unmapped]      0

Remove [unmapped] from result files

source-map-explorer script.js --tsv --only-mapped
Source  Size
[sourceMappingURL]      2308
node_modules/browser-pack/_prelude.js   480
src/bar.js      104
src/foo.js      97

Remove [sourceMappingURL] from result files

source-map-explorer script.js --tsv --exclude-source-map
Source  Size
node_modules/browser-pack/_prelude.js   480
src/bar.js      104
src/foo.js      97
[unmapped]      1

Do not remove common path prefix

source-map-explorer script.js --tsv --no-root

On error

Errors will be displayed only if no output flags specified

source-map-explore with-unmapped.js no-map-comment.js
no-map-comment.js
  Unable to find a source map.
  See https://github.com/danvk/source-map-explorer/blob/master/README.md#generating-source-maps
with-unmapped.js
  Unable to map 274/1335 bytes (20.52%)

API

explore(bundlesAndFileTokens, [options])

bundlesAndFileTokens:

  • Glob: dist/js/*.*
  • Filename: dist/js/chunk.1.js
  • Bundle: { code: 'dist/js/chunk.1.js', map: 'dist/js/chunk.1.js.map' } or { code: fs.readFileSync('dist/js/chunk.2.js') }
  • Array of globs, filenames and bundles:
    [
      'dist/js/chunk.2.*',
      'dist/js/chunk.1.js', 'dist/js/chunk.1.js.map',
      { code: 'dist/js/chunk.3.js', map: 'dist/js/chunk.3.js.map' }
    ]
    

options:

  • onlyMapped: boolean (default false) - Exclude "unmapped" bytes from the output. This will result in total counts less than the file size
  • excludeSourceMapComment: boolean (default false) - Exclude source map comment size from output. This will result in total counts less than the file size.
  • output: Object - Output options
    • format: string - 'json', 'tsv' or 'html'
    • filename: string - Filename to save output to
  • noRoot: boolean (default false) - See --no-root option above for details
  • noBorderChecks: boolean - Disable invalid mapping column/line checks. See --no-border-checks above.
  • replaceMap: <Object<{ [from: string]: string }>> - Mapping for replacement, see --replace, --with options above for details.
  • coverage: string - If the path to a valid a chrome code coverage JSON export is supplied, the tree map will be colorized according to which percentage of the modules code was executed
  • gzip: boolean - Calculate gzip size. It also sets onlyMapped flag

Example:

import { explore } from 'source-map-explorer'
// or import explore from 'source-map-explorer'

explore('tests/data/foo.min.js', { output: { format: 'html' } }).then()

// Returns
{
  bundles: [{
    bundleName: 'tests/data/foo.min.js',
    totalBytes: 718,
    unmappedBytes: 1,
    mappedBytes: 681,
    eolBytes: 1,
    sourceMapCommentBytes: 35,
    files: {
      'node_modules/browserify/node_modules/browser-pack/_prelude.js': { size: 480 },
      'dist/bar.js': { size: 104 },
      'dist/foo.js': { size: 97 },
      '[sourceMappingURL]': { size: 35 },
      '[unmapped]': { size: 1 },
      '[EOLs]': { size: 1 }
    }
  }],
  output: '<!doctype html>...',
  errors: []
}

See more at wiki page

More details

explore(bundlesAndFileTokens, [options])

Returns Promise that is resolved to an object with properties:

  • bundles: array - List of bundle explore result objects
    • bundleName: string - Path associated with the bundle
    • totalBytes: number - Size of the provided file
    • unmappedBytes: number | undefined
    • eolBytes: number - Bytes taken by end of line characters
    • sourceMapCommentBytes: number - sourceMappingURL comment bytes
    • files: { [sourceFile: string]: number }[] - Map containing filenames from the source map and size in bytes they take inside of provided file. Additional key <unmapped> is included if options.onlyMapped is false.
  • output: string - Result as a string if output.format options specified. If output='html' it contains self-packed HTML that can be opened in the browser
  • errors: array - List of bundle explore error objects
    • bundleName: string - Path associated with the bundle
    • code: string - Error code
    • message: string - User friendly message
    • error: Error
    • isWarning: boolean - Whether error isn't fatal

The promise is rejected when there is a fatal error or all bundles explore failed. Reject reason is either explore result object (the same one returned when promise resolved) or Error object with code property specified.

Possible error codes are:

  • Unknown
  • NoBundles - Empty array is passed to explore
  • OneSourceSourceMap - Bundle source map only contains one source
  • UnmappedBytes (warning) - There are unmapped bytes
  • InvalidMappingLine - Source map refers to the generated line beyond source last line
  • InvalidMappingColumn - Source map refers to generated column beyond source last column at the line
  • CannotSaveFile - Error saving HTML to file when file option specified
  • CannotCreateTempFile - Temporary HTLM file with visualization cannot be created. Check temporary folder access.
  • CannotOpenTempFile - Temporary HTLM file cannot be opened. Check if default browser can openen html files.
  • CannotOpenCoverageFile - Unable to open/parse coverage file
  • NoCoverageMatches - No matched bundles found for coverages.
  • Node.js error code (e.g. 'ENOENT')

Examples

Full

explore('js/*.*', {
  file: './sme-results/2019-04-27.html',
  output: {
    format: 'html',
    filename: './sme-result.html'
  },
  noRoot: true,
  onlyMapped: true,
  replaceMap: {
    dist: ''
  }
})
  .then((result: ExploreResult) => {
    result.errors.forEach((error: ExploreErrorResult) => {
      if (error.isWarning) {
        console.log(`Issue during '${error.bundleName}; explore`, error.message);
      } else {
        console.log(`Failed to explore '${error.bundleName}'`, error.message);
      }
    });

    result.bundles.forEach((bundle: ExploreBundleResult) => {
      console.log(bundle.bundleName);
      console.log(JSON.stringify(bundle.files));
    });
  })
  .catch(error => {
    console.log('Failed to explore');
    if (error.errors) {
      error.errors.forEach((exploreError: ExploreErrorResult) => {
        console.log(exploreError.bundleName);
        console.log(exploreError.message);
      });
    } else {
      console.log(error);
    }
  });

Inline or referenced map

explore('with-inline-map.js');

Separate map

explore(['foo.min.js', 'foo.min.js.map']);

Glob pattern

explore('js/*.*');

Multiple globs

explore(['js/foo.1*.js', 'js/foo.mi?.js']);

Specify bundles explicitly

const bundle: Bundle = { code: 'foo.min.js', map: 'foo.min.js.map' };

explore(bundle);

explore([{ code: 'foo.min.js', map: 'foo.min.js.map' }, { code: 'with-inline-map.js' }]);

Pass buffer

explore({ code: fs.readFileSync('js/foo.min.js'), map: fs.readFileSync('js/foo.min.js.map') });

gzip size

When gzip option (or --gzip parameter) is specified result size calculated as gzip size. Due to the nature of compression a gzip file size is inaccurate. It means that removing a 1k gzipped file in a bundle may reduce the bundle size by less than 1k. Also it's impossible to calculate unmapped bytes because the sum of spans' gzip sizes isn't equal to gzip size of the source file.

Code coverage heat map

In Google Chrome, you can collect code coverage stats. source-map-explorer accepts path to via --coverage argument (or coverage API option) and attempts to color code the heat map. This allows you to find the code that is not strictly needed for the initial page load and helps to identify the ideal ways to code split.

Red boxes correspond to code that would only be executed if the user took some action, or if some condition was met. For example, it may be a component inside of a dropdown the user never interacted with, or components that are only needed if the user opens a modal. In cases where the parent is green but the boxes inside are red, that means maybe some "initialization" logic ran, but the inner code never ran. Maybe we mounted a button, but not the other components in that module that are only needed if and when the user clicks the button, in that case, I would have the button trigger the rest of the code to load.

The heat map feature helps you identify the code that is needed for a fast initial page load (green), as well as helps to identify the code that can be (potentially) deferred because it doesn't run until the user interacts with some feature (red).

What might contribute to a generated file size

In addition to mapped generated code a file may contain:

  • sourceMappingURL comment - A comment containing source map or referencing the file with source map. Represented by [sourceMappingURL] in explore result.
  • Mapped code without source. It might be code generated by a bundler (e.g. webpack). Represented by [no source] in explore result.
  • Unmapped code - code that is not referenced within the source map. Represented by [unmapped] in explore result. For example webpack keeps on-demand chunk's content unmapped.

Generating source maps

For source-map-explorer to be useful, you need to generate a source map which maps positions in your minified file all the way back to the files from which they came.

If you use browserify, you can generate a JavaScript file with an inline source map using the --debug flag:

browserify -r .:foo --debug -o foo.bundle.js
source-map-explorer foo.bundle.js

If you subsequently minify your JavaScript, you'll need to ensure that the final source map goes all the way back to the original files. For example, using browserify, uglify and exorcist:

browserify -r .:foo --debug -o foo.bundle.js
# foo.bundle.js has an inline source map
cat foo.bundle.js | exorcist foo.bundle.js.map > /dev/null
# foo.bundle.js.map is an external source map for foo.bundle.js
uglifyjs -c -m \
  --in-source-map foo.bundle.js.map \
  --source-map foo.min.js.map \
  -o foo.min.js \
  foo.bundle.js
# foo.min.js has an external source map in foo.min.js.map
source-map-explorer foo.min.js

Types of source maps

There are two types of source maps: inline and external.

If your JS file has an inline source map, then its last line will look something like this:

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJm...

This encodes the sourcemap as a base64 data URL. If your file has an inline source map, the source-map-explorer should have no trouble understanding it.

If your last line instead looks like this:

//# sourceMappingURL=foo.min.js.map

Then the source map lives in an external .map file. The source-map-explorer will try to find this file, but this often fails because it's unclear what the URL is relative to.

If this happens, just pass in the source map explicitly, e.g. (in bash or zsh):

source-map-explorer path/to/foo.min.js{,.map}

Other source map tools

Learn about source maps

More Repositories

1

dygraphs

Interactive visualizations of time series using JavaScript and the HTML canvas tag
JavaScript
3,170
star
2

effective-typescript

Effective TypeScript 2nd Edition: 83 Specific Ways to Improve Your TypeScript
1,555
star
3

oldnyc

Mapping photos of Old New York
Python
288
star
4

webdiff

Two-column web-based git difftool
Python
247
star
5

localturk

Mechanical Turk on your own machine.
TypeScript
199
star
6

RangeHTTPServer

SimpleHTTPServer with support for Range requests
Python
153
star
7

mocha-react

Demo of using MochaJS to test a ReactJS component (with JSX and Harmony)
JavaScript
116
star
8

pg-to-ts

Generate TypeScript interface definitions from your Postgres schema
TypeScript
107
star
9

literate-ts

Code samples that scale
TypeScript
103
star
10

crosswalk

Typed express router for TypeScript
TypeScript
88
star
11

typings-checker

Positive and negative assertions about TypeScript types and errors
TypeScript
49
star
12

jss

JSON processing command line tool based on JSONSelect (CSS-like selectors for JSON)
Python
44
star
13

codediff.js

Two-column JavaScript diff visualization with syntax highlighting
JavaScript
41
star
14

ssi-server

Server Side Includes in Python's SimpleHTTPServer
Python
38
star
15

gwbasic-decoder

Convert binary GW-BASIC programs back to text
Python
36
star
16

crudely-typed

Simple "everyday CRUD" Postgres queries with perfect TypeScript types
TypeScript
35
star
17

travis-weigh-in

Track how each commit/pull request affects the size of a file in your repo
Python
32
star
18

dygraphs-es6

Dygraphs ES6 import demonstration
JavaScript
31
star
19

webtreemap

CLI to quickly get a treemap visualization
TypeScript
29
star
20

boxedit

A web-based editor for Tesseract box files
JavaScript
28
star
21

dtsearch

Find packages with TypeScript types, either bundled or on Definitely Typed
TypeScript
25
star
22

march-madness-data

NCAA brackets in JSON form
HTML
21
star
23

any-xray

X-Ray Glasses for TypeScript any Types
TypeScript
19
star
24

aoc2021

Advent of Code 2021, this time in Go
Go
17
star
25

expandable-image-grid

A grid of expandable images, similar to Google Image Search
JavaScript
16
star
26

extract-raster-network

Extract a network graph (nodes and edges) from a raster image
Python
15
star
27

sfhistory

Making a map of historical SF photos
JavaScript
14
star
28

historymaps

Creating interactive historical maps of the world
Python
13
star
29

comparea

Compare geographic features
Python
12
star
30

pyjsonselect

Fully conformant implementation of JSONSelect in Python
Python
11
star
31

better-pull-requests

An improved UI for github pull requests
JavaScript
10
star
32

dds.js

Bridge Double Dummy Solver in JavaScript
JavaScript
9
star
33

lonely

Lonely Hangouts: local testing for the Google+ Hangout API
JavaScript
8
star
34

ts-mover

Utility for moving and renaming files in a TypeScript project
TypeScript
8
star
35

github-syntax

Add syntax highlighting to github's split diff pull request UI
JavaScript
7
star
36

performance-boggle

An attempt to find the highest-scoring Boggle board and prove that I've found it.
C++
6
star
37

aoc2023

Advent of Code 2023
Zig
6
star
38

aoc2022

Advent of Code, this time in Deno
TypeScript
6
star
39

deepsea

Reimplementation of DeepSEA using TensorFlow
Python
6
star
40

personal-archive

Organize your digital debris
Python
5
star
41

aoc2020

Advent of Code 2020 (in Rust)
Rust
5
star
42

prop-drilling

Examples of prop drilling in React+TypeScript, along with ways to mitigate it
TypeScript
4
star
43

danvk.github.io

danvk github pages
HTML
4
star
44

advent2019

Solutions for Advent of Code 2019
Python
4
star
45

justchartit

When you want to see charts, just chart it!
TypeScript
4
star
46

crosswalk-demo

Demo of typed-router
TypeScript
4
star
47

dtslint-post

Medium post on how to use dtslint
TypeScript
3
star
48

obesity

Analysis of government obesity data
Python
3
star
49

github-h-index

Calculating the h-index of organizations and users on GitHub
Python
3
star
50

gravlax

A Lox interpreter with tasty TypeScript seasoning
TypeScript
3
star
51

react-inputs-toy

A toy demonstration of uncontrolled inputs in React.js
JavaScript
3
star
52

stub-require

Automatically create stubbed-out versions of node.js modules, ala Jest's autoMock
JavaScript
3
star
53

catskills

Hiking the skillies
TypeScript
2
star
54

geojson-proto

Demo of JSON and proto serialization using protobuf.js and TypeScript
TypeScript
2
star
55

gpt-batch-manager

Tools for splitting jobs across multiple OpenAI batches
Python
2
star
56

issue-tracker

Chart open GitHub issues over time
Python
2
star
57

async-iteration

Experiments with async iterators
TypeScript
2
star
58

maven-hadoop-quickstart

Simple repo containing a Hadoop MapReduce configured via Maven
Java
1
star
59

rusty-boggle

A boggle solver in Rust
Rust
1
star
60

strict-function-types-demo

Demo of strictFunctionTypes in TS 2.6
TypeScript
1
star
61

historytoy

Crafting navigation in a single page web application
JavaScript
1
star
62

dtslint-demo

Testing type declarations
TypeScript
1
star
63

dygraphs-dpxdt

dpxdt screenshots for dygraphs charting library
1
star
64

lstm-examples

Tooling around with LSTM
Python
1
star
65

reactjs-prop-validation

A ReactJS Mixin which enforces that a Component document all its props in propTypes.
JavaScript
1
star
66

bogglets

Boggle in TypeScript
TypeScript
1
star
67

ts-bench

Performance testing tool for the TypeScript compiler
Python
1
star
68

slamrank

Calculate rankings after an ongoing Grand Slam
JavaScript
1
star
69

bridge-filter

Simplify ACBL Club Results pages
Python
1
star
70

testing-types

Demo code for my talk at tsconf 2019
1
star
71

ts-reftrans

Demonstration of issues around referential transparency and type inference in TypeScript
TypeScript
1
star
72

knip-unused-member-repro

Repro for issue with knip
TypeScript
1
star