microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1c95903252fe6e7254eb74d73ff613baaa4610ed

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

204lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import { logger } from "vscode-debugadapter";
5import { ensurePackagerRunning } from "../common/packagerStatus";
6import path = require("path");
7import { Request } from "../common/node/request";
8import { SourceMapUtil } from "./sourceMap";
9import url = require("url");
10import * as semver from "semver";
11import { ProjectVersionHelper, RNPackageVersions } from "../common/projectVersionHelper";
12import { ErrorHelper } from "../common/error/errorHelper";
13import { InternalErrorCode } from "../common/error/internalErrorCode";
14import { FileSystem } from "../common/node/fileSystem";
15
16export interface DownloadedScript {
17 contents: string;
18 filepath: string;
19}
20
21interface IStrictUrl extends url.Url {
22 pathname: string;
23 href: string;
24}
25
26export class ScriptImporter {
27 public static DEBUGGER_WORKER_FILE_BASENAME = "debuggerWorker";
28 public static DEBUGGER_WORKER_FILENAME = ScriptImporter.DEBUGGER_WORKER_FILE_BASENAME + ".js";
29 private packagerAddress: string;
30 private packagerPort: number;
31 private sourcesStoragePath: string;
32 private packagerRemoteRoot?: string;
33 private packagerLocalRoot?: string;
34 private sourceMapUtil: SourceMapUtil;
35
36 constructor(
37 packagerAddress: string,
38 packagerPort: number,
39 sourcesStoragePath: string,
40 packagerRemoteRoot?: string,
41 packagerLocalRoot?: string,
42 ) {
43 this.packagerAddress = packagerAddress;
44 this.packagerPort = packagerPort;
45 this.sourcesStoragePath = sourcesStoragePath;
46 this.packagerRemoteRoot = packagerRemoteRoot;
47 this.packagerLocalRoot = packagerLocalRoot;
48 this.sourceMapUtil = new SourceMapUtil();
49 }
50
51 public downloadAppScript(
52 scriptUrlString: string,
53 projectRootPath: string,
54 ): Promise<DownloadedScript> {
55 const parsedScriptUrl = url.parse(scriptUrlString);
56 const overriddenScriptUrlString =
57 parsedScriptUrl.hostname === "localhost"
58 ? this.overridePackagerPort(scriptUrlString)
59 : scriptUrlString;
60 // We'll get the source code, and store it locally to have a better debugging experience
61 return Request.request(overriddenScriptUrlString, true).then(scriptBody => {
62 return ProjectVersionHelper.getReactNativeVersions(projectRootPath).then(rnVersions => {
63 // unfortunatelly Metro Bundler is broken in RN 0.54.x versions, so use this workaround unless it will be fixed
64 // https://github.com/facebook/metro/issues/147
65 // https://github.com/microsoft/vscode-react-native/issues/660
66 if (
67 ProjectVersionHelper.getRNVersionsWithBrokenMetroBundler().indexOf(
68 rnVersions.reactNativeVersion,
69 ) >= 0
70 ) {
71 let noSourceMappingUrlGenerated =
72 scriptBody.match(/sourceMappingURL=/g) === null;
73 if (noSourceMappingUrlGenerated) {
74 let sourceMapPathUrl = overriddenScriptUrlString.replace("bundle", "map");
75 scriptBody = this.sourceMapUtil.appendSourceMapPaths(
76 scriptBody,
77 sourceMapPathUrl,
78 );
79 }
80 }
81
82 // Extract sourceMappingURL from body
83 let scriptUrl = <IStrictUrl>url.parse(overriddenScriptUrlString); // scriptUrl = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true"
84 let sourceMappingUrl = this.sourceMapUtil.getSourceMapURL(scriptUrl, scriptBody); // sourceMappingUrl = "http://localhost:8081/index.ios.map?platform=ios&dev=true"
85
86 let waitForSourceMapping: Promise<void> = Promise.resolve();
87 if (sourceMappingUrl) {
88 /* handle source map - request it and store it locally */
89 waitForSourceMapping = this.writeAppSourceMap(sourceMappingUrl, scriptUrl).then(
90 () => {
91 scriptBody = this.sourceMapUtil.updateScriptPaths(
92 scriptBody,
93 <IStrictUrl>sourceMappingUrl,
94 );
95 if (semver.gte(rnVersions.reactNativeVersion, "0.61.0")) {
96 scriptBody = this.sourceMapUtil.removeSourceURL(scriptBody);
97 }
98 },
99 );
100 }
101
102 return waitForSourceMapping
103 .then(() => this.writeAppScript(scriptBody, scriptUrl))
104 .then((scriptFilePath: string) => {
105 logger.verbose(
106 `Script ${overriddenScriptUrlString} downloaded to ${scriptFilePath}`,
107 );
108 return { contents: scriptBody, filepath: scriptFilePath };
109 });
110 });
111 });
112 }
113
114 public downloadDebuggerWorker(
115 sourcesStoragePath: string,
116 projectRootPath: string,
117 debuggerWorkerUrlPath?: string,
118 ): Promise<void> {
119 const errPackagerNotRunning = ErrorHelper.getInternalError(
120 InternalErrorCode.CannotAttachToPackagerCheckPackagerRunningOnPort,
121 this.packagerPort,
122 );
123 return ensurePackagerRunning(this.packagerAddress, this.packagerPort, errPackagerNotRunning)
124 .then(() => {
125 return ProjectVersionHelper.getReactNativeVersions(projectRootPath);
126 })
127 .then((rnVersions: RNPackageVersions) => {
128 let debuggerWorkerURL = this.prepareDebuggerWorkerURL(
129 rnVersions.reactNativeVersion,
130 debuggerWorkerUrlPath,
131 );
132 let debuggerWorkerLocalPath = path.join(
133 sourcesStoragePath,
134 ScriptImporter.DEBUGGER_WORKER_FILENAME,
135 );
136 logger.verbose(
137 "About to download: " + debuggerWorkerURL + " to: " + debuggerWorkerLocalPath,
138 );
139
140 return Request.request(debuggerWorkerURL, true).then((body: string) => {
141 return new FileSystem().writeFile(debuggerWorkerLocalPath, body);
142 });
143 });
144 }
145
146 public prepareDebuggerWorkerURL(rnVersion: string, debuggerWorkerUrlPath?: string): string {
147 let debuggerWorkerURL: string;
148 // It can be empty string
149 if (debuggerWorkerUrlPath !== undefined) {
150 debuggerWorkerURL = `http://${this.packagerAddress}:${this.packagerPort}/${debuggerWorkerUrlPath}${ScriptImporter.DEBUGGER_WORKER_FILENAME}`;
151 } else {
152 let newPackager = "";
153 if (
154 !semver.valid(
155 rnVersion,
156 ) /*Custom RN implementations should support new packager*/ ||
157 semver.gte(rnVersion, "0.50.0")
158 ) {
159 newPackager = "debugger-ui/";
160 }
161 debuggerWorkerURL = `http://${this.packagerAddress}:${this.packagerPort}/${newPackager}${ScriptImporter.DEBUGGER_WORKER_FILENAME}`;
162 }
163 return debuggerWorkerURL;
164 }
165
166 /**
167 * Writes the script file to the project temporary location.
168 */
169 private writeAppScript(scriptBody: string, scriptUrl: IStrictUrl): Promise<string> {
170 let scriptFilePath = path.join(this.sourcesStoragePath, path.basename(scriptUrl.pathname)); // scriptFilePath = "$TMPDIR/index.ios.bundle"
171 return new FileSystem().writeFile(scriptFilePath, scriptBody).then(() => scriptFilePath);
172 }
173
174 /**
175 * Writes the source map file to the project temporary location.
176 */
177 private writeAppSourceMap(sourceMapUrl: IStrictUrl, scriptUrl: IStrictUrl): Promise<void> {
178 return Request.request(sourceMapUrl.href, true).then((sourceMapBody: string) => {
179 let sourceMappingLocalPath = path.join(
180 this.sourcesStoragePath,
181 path.basename(sourceMapUrl.pathname),
182 ); // sourceMappingLocalPath = "$TMPDIR/index.ios.map"
183 let scriptFileRelativePath = path.basename(scriptUrl.pathname); // scriptFileRelativePath = "index.ios.bundle"
184 let updatedContent = this.sourceMapUtil.updateSourceMapFile(
185 sourceMapBody,
186 scriptFileRelativePath,
187 this.sourcesStoragePath,
188 this.packagerRemoteRoot,
189 this.packagerLocalRoot,
190 );
191 return new FileSystem().writeFile(sourceMappingLocalPath, updatedContent);
192 });
193 }
194
195 /**
196 * Changes the port of the url to be the one configured as this.packagerPort
197 */
198 private overridePackagerPort(urlToOverride: string): string {
199 let components = url.parse(urlToOverride);
200 components.port = this.packagerPort.toString();
201 delete components.host; // We delete the host, if not the port change will be ignored
202 return url.format(components);
203 }
204}