In this article, I will explain what SourceMap does, how it works, how different Devtools in WebPack build SourceMap, and how to choose the most suitable devtool.

Understand SourceMap

SourceMap role

With the growing number of packaging tools, the code in our online environment generally goes through the following processes in order to improve front-end project performance and compatibility across browsers:

  • Compressing confusion, reducing volume
  • Multiple files are merged to reduce the number of HTTP requests
  • Convert ES6 + code into ES5 code that the browser can recognize

After the above steps, the performance and compatibility of our code improved, and then it became difficult to debug because the converted code was different from the source code. SourceMap was created to solve these problems.

In short, SourceMap is a file that stores information about the location of the code, each location of the converted code, corresponding to the location before the transformation. With this, when you click on the browser console to report an error message, you can display the error source location directly instead of the transformed code.

As an example, here’s a simple index.js file where we deliberately misspelled the last line console.log(‘hello world’) as console.logo(‘hello world’) :

const a = 1;
const b = 2;
console.log(a + b);
console.logo('hello world');
Copy the code

No SourceMap

We set the devtool option for webpack.config.js to ‘None’ and package the above index.js file:

// ...
module.exports = {
  // ...
  mode: 'production'.devtool: 'none'.// ...
};
Copy the code

Click the console error code as follows, you can see that the code is compressed after confusion, we are difficult to trace the error source code:

Have a SourceMap

After changing the devtool configuration of webpack.config.js from ‘none’ to source-map, pack the index.js file again:

// ...
module.exports = {
  // ...
  mode: 'production'.devtool: 'source-map'.// ...
};
Copy the code

Click the error message on the console, we can see the source code displayed, we can clearly locate the error line number, and the cursor directly stays on the error code column:

It is easy to understand the benefits of SourceMap from the above comparison. So how does SourceMap work in the browser?

How SourceMap works

After we use Webpack packaging and select DevTool as source-map, each packaged JS module will have a corresponding. Map file:

The packaged main.js.map file is a standard SourceMap content format:

{
  "version": 3."sources": [
    "webpack:///webpack/bootstrap"."webpack:///./src/index.js"."webpack:///./src/add.js"]."names": [
    // ...
    "p"."s"."console"."log"."a"."b"."add"]."mappings": "aACE,IAAIA,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUC,QAGnC,IAAIC,EAASJ,EAAiBE,GAAY,CACzCG,EA AGH,EACHI,GAAG,EACHH,QAAS,IAUV,OANAI,EAAQL,GAAUM,KAAKJ,EAAOD,QAASC,EAAQA,EAAOD,QAASF,GAG/DG,EAAOE,GAAI,EAGJF,EAAOD,QAKfF ,EAAoBQ,EAAIF,EAGxBN,EAAoBS,EAAIV,EAGxBC,EAAoBU,EAAI,SAASR,EAASS,EAAMC,GAC3CZ,EAAoBa,EAAEX,EAASS,IAClCG,OAAOC,eAAeb,EAAS S,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEZ,EAAoBkB,EAAI,SAAShB,GACX,oBAAXiB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAeb,EAASiB,OAAOC,YAAa, CAAEC,MAAO,WAE7DP,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,KAQvDrB,EAAoBsB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQrB,EAAoB qB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB ,GAFA1B,EAAoBkB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAO N,EAAOrB,EAAoBU,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRzB,EAAoB6B,EAAI,SAAS1B,GAChC,IA AIS,EAAST,GAAUA,EAAOqB,WAC7B,WAAwB,OAAOrB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAH,EAAoBU,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRZ,EAAoB a,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG/B,EAAoBkC,EAAI,GAIjBlC,EAAoBA,EAAoBmC,EAAI,G,sCC/ ErDC,QAAQC,ICHW,SAACC,EAAGC,GACrB,OAAOD,EAAIC,EDEDC,CAFF,EACA,IAEVJ,QAAQC,IAAI"."file": "main.js"."sourcesContent": [
    " \t// The module cache\n \tvar installedModules = {}; \n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports; \n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t}; \n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__); \n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true; \n\n \t\t// Return the exports of the module\n \t\treturn module.exports; \n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules; \n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules; \n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(! __webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter }); \n \t\t}\n \t}; \n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol ! == 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); \n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true }); \n \t}; \n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value); \n \t\tif(mode & 8) return value; \n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; \n \t\tvar ns = Object.create(null); \n \t\t__webpack_require__.r(ns); \n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value }); \n \t\tif(mode & 2 && typeof value ! = 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); \n \t\treturn ns; \n \t}; \n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ? \n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; }; \n \t\t__webpack_require__.d(getter, 'a', getter); \n \t\treturn getter; \n \t}; \n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; \n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\"; \n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0); \n"."import { add } from './add'; \nconst a = 1; \nconst b = 2; \nconsole.log(add(a, b)); \nconsole.log('hello world'); \n"."export const add = (a, b) => {\n return a + b; \n}; \n"]."sourceRoot": ""
}
Copy the code

It contains the following:

  • Version: Indicates the SourceMap version. The latest version is 3
  • Sources: indicates the source file list
  • Names: Variable names in the source file
  • Mappings: The location information of the source code that has been compressed and confused
  • File: indicates the name of the file corresponding to the Source Map
  • SourcesContent: A list of source code strings used to display source files during debugging, each item in the list corresponding to Sources
  • SourceRoot: The root directory of the source file, which is appended to each source file

//# sourceMappingURL=main.js.map

!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1.exports: {}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0.get:r})},n.r=function(e){"undefined"! =typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule", {value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"= =typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default", {enumerable:!0.value:e}),2&t&&"string"! =typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)} ([function(e,t){console.log(3),console.log("hello world")}]);
//# sourceMappingURL=main.js.map
Copy the code

When the browser loads main.js, it loads the corresponding. Map file through sourceMappingURL, and generates the corresponding directory structure in the sources field of the SourceMap content of the. Map file. We then fill the sourcesContent into the generated file so that we can locate the compressed and obfuscated code to the corresponding source code location during debugging.

If devtool is selected as inline-source-map, the content after sourceMappingURL is embedded as Base64.

Webpack devtool options

According to the webPack documentation, Devtool has nearly 30 configurable options:

devtool performance production quality
(none) build: fastest rebuild: fastest yes bundle
eval build: fast rebuild: fastest no generated
eval-cheap-source-map build: ok rebuild: fast no transformed
eval-cheap-module-source-map build: slow rebuild: fast no original lines
eval-source-map build: slowest rebuild: ok no original
cheap-source-map build: ok rebuild: slow no transformed
cheap-module-source-map build: slow rebuild: slow no original lines
source-map build: slowest rebuild: slowest yes original
inline-cheap-source-map build: ok rebuild: slow no transformed
inline-cheap-module-source-map build: slow rebuild: slow no original lines
inline-source-map build: slowest rebuild: slowest no original
eval-nosources-cheap-source-map build: ok rebuild: fast no transformed
eval-nosources-cheap-module-source-map build: slow rebuild: fast no original lines
eval-nosources-source-map build: slowest rebuild: ok no original
inline-nosources-cheap-source-map build: ok rebuild: slow no transformed
inline-nosources-cheap-module-source-map build: slow rebuild: slow no original lines
inline-nosources-source-map build: slowest rebuild: slowest no original
nosources-cheap-source-map build: ok rebuild: slow no transformed
nosources-cheap-module-source-map build: slow rebuild: slow no original lines
nosources-source-map build: slowest rebuild: slowest yes original
hidden-nosources-cheap-source-map build: ok rebuild: slow no transformed
hidden-nosources-cheap-module-source-map build: slow rebuild: slow no original lines
hidden-nosources-source-map build: slowest rebuild: slowest yes original
hidden-cheap-source-map build: ok rebuild: slow no transformed
hidden-cheap-module-source-map build: slow rebuild: slow no original lines
hidden-source-map build: slowest rebuild: slowest yes original

We configure different devtool options in the table for different quality and performance purposes

The understanding of the quality

Quality describes the source code we can see when debugging after packaging:

  • Bundled: Modules unbundled
  • Generated: codes generated by modules that are not processed by loader
  • Built: Separated modules, code processed by the Loader
  • Original: self-written code, positioning accurate to the row, column
  • Original lines: Code written by yourself, positioning only accurate to the line

Devtool format

The devtool name format can be summarized as follows: We can easily understand all devtool options by remembering the characteristics of each of the following options:

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
Copy the code

inline-

Inlining SourceMap into the original file rather than creating a separate file.

Devtool for source-map cases after packaging, there is a. Map file to store code mapping:

When devtool is inline-source-map, the mapping is written to the compiled code together:

hidden-

Hidden still generates the.map file, but there is no sourceMappingURL in the packaged code, meaning the browser will not load the.map file when the code is requested, and the source code is not available in the console. This is usually used in error collection scenarios. When an error occurs, the front end passes the error column to the server, and the server resolves the source code location of the error according to the column and the. Map file.

Error source code location displayed when devtool is source-map:

When devtool is hidden-source-map, only the error location of the packaged code is displayed:

eval-

Eval – Wraps each module’s packaged code and the corresponding generated SourceMap through EVAL. Because eval is in string form, string processing when the source code changes will improve the speed of rebuild.

However, as eval wraps JS code, it is easy to be attacked by XSS, causing great security risks.

In addition, there are two compilation modes available in modern browsers: Fast Path and Slow Path. Fast Path compiles code that is stable and predictable. Obviously eval is unpredictable, so slow path will be used. In older browsers, performance with Eval was drastically reduced.

To sum up, eval is generally used only in development environments, not for packaging code in online environments.

nosources-

The SourceMap generated with this keyword does not contain sourcesContent, so only file information and line information are seen during debugging, not the source code.

cheap-[module-]

With Cheap, SourceMap’s code location is only on the row where the source code resides, not on the specific column, so the build speed is improved. If only cheap is used, the source code compiled by the Loader is displayed. If module is added, the source code compiled by the Loader is displayed.

For example, there is the following code:

import { add } from './add';
const a = 1;
const b = 2;
console.log(add(a, b));
console.logo(111);
console.log('hello world');
Copy the code

Use source-map to package the result, click the error message on the console, you can see the source code directly located to the loader before compiling, and the cursor will locate the error code column:

Using the cheap-source-map package, clicking on the console error message locates the line of the error code, but the cursor does not locate the column of the error code. In addition, the source code displayed is the code compiled by loader, not the original source code:

Use cheap-module-source-map package, click the console error message, you can see the loader before compiling source code:

How do I select DevTool

Depending on the environment, we need to choose a different Devtool.

production

There are four devtools recommended for online environments:

  • none
  • source-map
  • hidden-source-map
  • nosources-source-map

There is no absolute optimal choice in the online environment. You can choose according to your business needs. Many projects also choose the cheap-module-source-map option in addition to the above four options.

development

The choice of development environment is relatively easy, just need to consider the packaging speed, convenient debugging, the official recommendation of the following four:

  • eval
  • eval-source-map
  • eval-cheap-source-map
  • eval-cheap-module-source-map

For the most part, eval-cheap-module-source-map is just fine.

conclusion

To sum up, in the development environment, we pay more attention to the rebuild performance in Performance because we need to modify the code frequently and consider the development efficiency and debugging efficiency more.

In the production environment, we do not need to pay too much attention to packaging performance, and mainly consider the protection of quality code, error location speed and security.

Because different teams have different requirements for online code, they can choose flexibly according to their own business.