Skip to content

Instantly share code, notes, and snippets.

@frogcjn
Last active November 17, 2020 11:03
Show Gist options
  • Save frogcjn/3bb47ce01a5168d8cd570a46cc71bb02 to your computer and use it in GitHub Desktop.
Save frogcjn/3bb47ce01a5168d8cd570a46cc71bb02 to your computer and use it in GitHub Desktop.
How to make VScode debug Typescript file with ReactNative

If using Typescript to write a ReactNative Project in VSCode, you'll find that VSCode cannot stop at any breakpoint in TypeScript files. You must create breakpoints in JS files.

This post will answer why this problem appears and how to solve that.

Enviroment: VSCode (1.1.1 or 1.2.0) and ReactNative Tool Extension (0.1.4)

Solution Target: Debugger will work on break points in TypeScript files, in a ReactNative Project

Example Project structure:

index.ios.js             // This is the entrance of RN, this entrance leads to `./build/index.ios.js`, which is transformed from TS file
./src/index.ios.tsx      // This is the source code
./build/index.ios.js     // This is generated code
./nodemodules/**/**.js   // This is the JS code from npm

Notes:

  • tsx files will directly transform to js files, there is no jsx files.

    • So the jsx in tsconfig.json should be set to react, instead of reserve).
    • only in this way, you can use TypeScript writing ReactNative projects.
    • This is becuase tsc in reserve mode will transfer *.tsx to *.jsx file extension, and ReactNative refuse to recognize *.jsx file extension. See facebook/react-native#5233 (comment)
  • And also it is better to keep an index.ios.js as an pure entrance to your TS code.

Resources

2016-06-07 19 31 45

The code transform process

  1. TypeScript files --(tsc)--> ES6 files, and source maps.
  2. ES6 files --(ReactNative Packager's transformer (using Babel))--> ES5 files, and source maps
  3. ES5 files --(ReactNative packager's bundler)--> index.ios.bundle & index.ios.map.
  4. VSCode ReactNative Tool copy index.ios.bundle & index.ios.map into .vscode/.react in your ReactNative project, and let paths in index.ios.map to be relative to it's folder path .vscode/.react.
  5. VSCode Node Debugger running index.ios.bundle and stop at right places of your break points. This needs helps from index.ios.map.

What's wrong?

###Problem 1: ReactNative Packager's transformer needs sourceMaps flag in .babelrc

Facebook ReactNative Packager's transformer , using Babel to transform ES6 files to normal ES5 files, plays an important role here.

However, this transformer let the example React Native project decides whether Babel should generate source map. So Babel will not generate source map if not set sourceMaps. Only if you set value with truly value in .babelrc file at your ReactNative project's workspace root, you can get the source map mapping between ES6 files and ES5 files.

###Problem 2: ReactNative Packager's bundler will generate fake source map if no input source map Wait a moment, there is no souremap between ES6 and ES5 files? But how VSCode still can stop at breakpoints in ES6 files without this source map? I have not set the sourceMapsflag in .babelrc but it works?

Because in step 3, ReactNative packager's bundler will generate fake simple line to line source map mapping between index.ios.bundle and ES5 files generated by Babel. The faked source map looks like:

  {
      "file": "index.ios.bundle",
      "sources": ["../../index.ios.js", "../../src/index.android.tsx", "../../node_modules/react/react.js"],
      "version": 3,
      "names": [],
      "mappings": "AAAA;AACA;AACA;AACA;AACA;ACJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA",
      "sourceRoot": ""
  }

It means each line of index.ios.bundle simply mapping to each line of ES5 generated by babel.

(You can use source map visualization to visualize this source map example:)

1:0 -> 1:0 in ../../index.ios.js
2:0 -> 2:0 in ../../index.ios.js
3:0 -> 3:0 in ../../index.ios.js
4:0 -> 4:0 in ../../index.ios.js
5:0 -> 5:0 in ../../index.ios.js
1:0 -> 6:0 in ../../src/index.android.tsx
2:0 -> 7:0 in ../../src/index.android.tsx
3:0 -> 8:0 in ../../src/index.android.tsx
4:0 -> 9:0 in ../../src/index.android.tsx
5:0 -> 10:0 in ../../src/index.android.tsx
6:0 -> 11:0 in ../../src/index.android.tsx
7:0 -> 12:0 in ../../src/index.android.tsx
8:0 -> 13:0 in ../../src/index.android.tsx
9:0 -> 14:0 in ../../src/index.android.tsx
10:0 -> 15:0 in ../../src/index.android.tsx
11:0 -> 16:0 in ../../src/index.android.tsx
...

This source map is lazy and imprecise, because Babel did not generate source map mapping between ES6 and ES5. Therefore, even if you set breakpoints in ES6 file, not in TS file, the debugger may still stop at wrong places.

OK. So how to solve this problem? We just need a .babelrc file with sourceMaps set to true in your ReactNative project.

After you doing that, you can see a correct source map at http://localhost:8081/index.ios.map?platform=ios&dev=true and.vscode/.react/index.ios.map in the example React Native project. There is a progress, Right?

While, you cannot set sourceMaps to inline and both, Why? Because ...

###Problem 3: VSCode React Native Tool, cannot deal index.ios.bundle with inline source maps

This means if you set sourceMaps set to inline or both, you'll get in trouble.

VSCode React Native Tool read index.ios.bundle, and find a first match of //# sourceMappingURL=*file url* to get the source map file index.ios.map. This works only with the case that source map are generated in a seperated file.

If there is multiple inline source map with DataURI format //@ sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9..., it will trigger errors.

I have no interest to solve this problem, so let us just set sourceMaps set to true in the example React Native project, ignore other posibilities. ​
But we still cannot stop any break point in TS files, and recently, we even cannot stop any break point in JS files!!! Why? Are we doing wrong? No! We are not doing nothing wrong. It is the fault of VSCode Node Debugger(Step 5)!! ​

Problem 4: VSCode Node Debugger, cannot read source map with sections

If we let ReactNative Packager's transformer (using Babel) generated source map in step 2, then the ReactNative Packager's bundler in step 3 will generated a source map with sections, source map with sections looks like this:

    {
      "version": 3,
      "file": "/index.ios.bundle?platform=ios&dev=true",
      "sections": [
        {
          "offset":{"line":1442,"column":0},
          "map": {
            "version":3,
            "file":"build/index.ios.js",
            "sourceRoot":"",
            "sources":["../src/index.ios.tsx"],
            "names":[],
            "mappings":";;;;;AAMO,gCAAK,AAAK,AAAM,AAAO,AAC9B,AAAM;;AACC,AACH,AAAU,AACV,AAAI,AACJ,AAAI,AACP,AAAM,AAAc,AAErB,6CAPO,AAAS,AAAC,UAAG,AAAK;;;AASjB,AAAM,AAAC,OACH,MAAC,AAAI,iCAAC,AAAK,MAAE,AAAM,OAAC,AAAU;AAC1B,MAAC,AAAI,iCAAC,AAAK,MAAE,AAAM,OAAC,AAAQ,SACxB,AACJ,AAAO;AACP,MAAC,AAAI,iCAAC,AAAK,MAAE,AAAM,OAAC,AAAa,cAC7B,AACJ,AAAO;AACP,MAAC,AAAI,iCAAC,AAAK,MAAE,AAAM,OAAC,AAAa;AAC7B,AAAuB;AAAC,AAAK;AAC7B,AACJ,AAAO,AACJ,AACV,AACL,gCAAC,AACL,AAAC,6BAjB2C,AAAS,AACjD,AAAM;;;AAkBV,IAAM,AAAM,OAAG,AAAU,wBAAC,AAAM;AAC5B,AAAS;AACL,AAAI,KAAE,AAAC,CADA;AAEP,AAAc,eAAE,AAAQ;AACxB,AAAU,WAAE,AAAQ;AACpB,AAAe,gBAAE,AAAS,AAC7B;;AACD,AAAO,QAAE;AACL,AAAQ,SAAE,AAAE;AACZ,AAAS,UAAE,AAAQ;AACnB,AAAM,OAAE,AAAE,AACb,GAX4B;;AAY7B,AAAY,aAAE;AACV,AAAS,UAAE,AAAQ;AACnB,AAAK,MAAE,AAAS;AAChB,AAAY,aAAE,AAAC,AAClB,AACJ,AAAC"
          }
        },
        {
          "offset":{"line":1478,"column":0},
          "map":...
        }

This kind of source map only supported by Chrome, not suported by VSCode Node Debugger, which is the built-in JS debugger of VSCode.

So we have two choices to solve this problem:

  1. make VSCode Node Debugger understand sectioned source map in step 5
  2. when VSCode React Native Tool copy index.ios.map into the project, which is step 4, flatten the source map with sections to a normal one.

Because it is easy to flatten source map with sections to a normal one using flatten-source-map package, I choose the 2nd approach the solve this problem. (Thanks to @DavidSouther to quickly publish version 0.0.2 of this package.)

So I change the behavior of VSCode React Native Tool, it now will flatten source map with sections to normal source map, so the VSCode Node Debugger can work correctly with source map in the folder .vscode/.react/ of your ReactNative project.

Note: don't worry about source map chain problem

  • tsc will generated a source map for mapping between TS and ES6

  • ReactNative Packager's transfer using Babel will generate source map for mapping ES6 and ES5

Do we need to combine this two source maps to make a final source map?

No, we don't need. Since Babel will automatically find #sourceMappingURL= in your ES6 files and read TS-ES6 source mapping and do the right things. It will generate a correct mapping between the source TS files and generated ES5 files.

@faisalil
Copy link

I cloned your enlistment. npm install, typings i and tried to run and see if it breaks but it doesn't. Am I missing any steps?

@evollu
Copy link

evollu commented Feb 17, 2017

@webzepter I have the same problem too. Did you find a solution?

@axelboc
Copy link

axelboc commented Jun 8, 2018

In case someone else lands here, I had this TypeError in VSCode's own dev tools console. My worspace's TypeScript version was set to 2.7.2. I fixed the issue by removing the override and letting VSCode use the latest 2.9.1 version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment