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 tojs
files, there is nojsx
files.- So the
jsx
intsconfig.json
should be set toreact
, instead ofreserve
). - only in this way, you can use TypeScript writing ReactNative projects.
- This is becuase
tsc
inreserve
mode will transfer*.tsx
to*.jsx
file extension, and ReactNative refuse to recognize*.jsx
file extension. See facebook/react-native#5233 (comment)
- So the
-
And also it is better to keep an
index.ios.js
as an pure entrance to your TS code.
-
Solution: You can directly clone and run the code to see the solution
-
Source map Tools
-
related GitHub repo
- [flatten-source-map](flatten-source-map/lib at master · DavidSout…)
- VSCode Node Debugger
- VSCode Extension: VSCode ReactNative Tool
- ReactNative Packager
-
related GitHub issue
- TypeScript files --(tsc)--> ES6 files, and source maps.
- ES6 files --(ReactNative Packager's transformer (using Babel))--> ES5 files, and source maps
- ES5 files --(ReactNative packager's bundler)-->
index.ios.bundle
&index.ios.map
. - VSCode ReactNative Tool copy
index.ios.bundle
&index.ios.map
into.vscode/.react
in your ReactNative project, and let paths inindex.ios.map
to be relative to it's folder path.vscode/.react
. - VSCode Node Debugger running
index.ios.bundle
and stop at right places of your break points. This needs helps fromindex.ios.map
.
###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 sourceMaps
flag 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)!!
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:
- make VSCode Node Debugger understand sectioned source map in step 5
- 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.
-
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.
If you just need to debug ts file with Chrome, without Vscode, you just can skip Problem 3, 4. What you need is just a
.babelrc
file withsourceMaps
set totrue
in your ReactNative project.