microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.7.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

194lines · 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 } 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 async 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
61 // We'll get the source code, and store it locally to have a better debugging experience
62 let scriptBody = await Request.request(overriddenScriptUrlString, true);
63
64 const rnVersions = await ProjectVersionHelper.getReactNativeVersions(projectRootPath);
65 // unfortunatelly Metro Bundler is broken in RN 0.54.x versions, so use this workaround unless it will be fixed
66 // https://github.com/facebook/metro/issues/147
67 // https://github.com/microsoft/vscode-react-native/issues/660
68 if (
69 ProjectVersionHelper.getRNVersionsWithBrokenMetroBundler().indexOf(
70 rnVersions.reactNativeVersion,
71 ) >= 0
72 ) {
73 let noSourceMappingUrlGenerated = scriptBody.match(/sourceMappingURL=/g) === null;
74 if (noSourceMappingUrlGenerated) {
75 let sourceMapPathUrl = overriddenScriptUrlString.replace("bundle", "map");
76 scriptBody = this.sourceMapUtil.appendSourceMapPaths(scriptBody, sourceMapPathUrl);
77 }
78 }
79
80 // Extract sourceMappingURL from body
81 let scriptUrl = <IStrictUrl>url.parse(overriddenScriptUrlString); // scriptUrl = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true"
82 let sourceMappingUrl = this.sourceMapUtil.getSourceMapURL(scriptUrl, scriptBody); // sourceMappingUrl = "http://localhost:8081/index.ios.map?platform=ios&dev=true"
83
84 let waitForSourceMapping: Promise<void> = Promise.resolve();
85 if (sourceMappingUrl) {
86 /* handle source map - request it and store it locally */
87 waitForSourceMapping = this.writeAppSourceMap(sourceMappingUrl, scriptUrl).then(() => {
88 scriptBody = this.sourceMapUtil.updateScriptPaths(
89 scriptBody,
90 <IStrictUrl>sourceMappingUrl,
91 );
92 if (semver.gte(rnVersions.reactNativeVersion, "0.61.0")) {
93 scriptBody = this.sourceMapUtil.removeSourceURL(scriptBody);
94 }
95 });
96 }
97 await waitForSourceMapping;
98 const scriptFilePath = await this.writeAppScript(scriptBody, scriptUrl);
99 logger.verbose(`Script ${overriddenScriptUrlString} downloaded to ${scriptFilePath}`);
100 return { contents: scriptBody, filepath: scriptFilePath };
101 }
102
103 public async downloadDebuggerWorker(
104 sourcesStoragePath: string,
105 projectRootPath: string,
106 debuggerWorkerUrlPath?: string,
107 ): Promise<void> {
108 const errPackagerNotRunning = ErrorHelper.getInternalError(
109 InternalErrorCode.CannotAttachToPackagerCheckPackagerRunningOnPort,
110 this.packagerPort,
111 );
112
113 await ensurePackagerRunning(this.packagerAddress, this.packagerPort, errPackagerNotRunning);
114
115 const rnVersions = await ProjectVersionHelper.getReactNativeVersions(projectRootPath);
116 let debuggerWorkerURL = this.prepareDebuggerWorkerURL(
117 rnVersions.reactNativeVersion,
118 debuggerWorkerUrlPath,
119 );
120 let debuggerWorkerLocalPath = path.join(
121 sourcesStoragePath,
122 ScriptImporter.DEBUGGER_WORKER_FILENAME,
123 );
124 logger.verbose(
125 "About to download: " + debuggerWorkerURL + " to: " + debuggerWorkerLocalPath,
126 );
127
128 const body = await Request.request(debuggerWorkerURL, true);
129
130 return new FileSystem().writeFile(debuggerWorkerLocalPath, body);
131 }
132
133 public prepareDebuggerWorkerURL(rnVersion: string, debuggerWorkerUrlPath?: string): string {
134 let debuggerWorkerURL: string;
135 // It can be empty string
136 if (debuggerWorkerUrlPath !== undefined) {
137 debuggerWorkerURL = `http://${this.packagerAddress}:${this.packagerPort}/${debuggerWorkerUrlPath}${ScriptImporter.DEBUGGER_WORKER_FILENAME}`;
138 } else {
139 let newPackager = "";
140 if (
141 !semver.valid(
142 rnVersion,
143 ) /*Custom RN implementations should support new packager*/ ||
144 semver.gte(rnVersion, "0.50.0")
145 ) {
146 newPackager = "debugger-ui/";
147 }
148 debuggerWorkerURL = `http://${this.packagerAddress}:${this.packagerPort}/${newPackager}${ScriptImporter.DEBUGGER_WORKER_FILENAME}`;
149 }
150 return debuggerWorkerURL;
151 }
152
153 /**
154 * Writes the script file to the project temporary location.
155 */
156 private async writeAppScript(scriptBody: string, scriptUrl: IStrictUrl): Promise<string> {
157 let scriptFilePath = path.join(this.sourcesStoragePath, path.basename(scriptUrl.pathname)); // scriptFilePath = "$TMPDIR/index.ios.bundle"
158 await new FileSystem().writeFile(scriptFilePath, scriptBody);
159 return scriptFilePath;
160 }
161
162 /**
163 * Writes the source map file to the project temporary location.
164 */
165 private async writeAppSourceMap(
166 sourceMapUrl: IStrictUrl,
167 scriptUrl: IStrictUrl,
168 ): Promise<void> {
169 const sourceMapBody = await Request.request(sourceMapUrl.href, true);
170 let sourceMappingLocalPath = path.join(
171 this.sourcesStoragePath,
172 path.basename(sourceMapUrl.pathname),
173 ); // sourceMappingLocalPath = "$TMPDIR/index.ios.map"
174 let scriptFileRelativePath = path.basename(scriptUrl.pathname); // scriptFileRelativePath = "index.ios.bundle"
175 let updatedContent = this.sourceMapUtil.updateSourceMapFile(
176 sourceMapBody,
177 scriptFileRelativePath,
178 this.sourcesStoragePath,
179 this.packagerRemoteRoot,
180 this.packagerLocalRoot,
181 );
182 return new FileSystem().writeFile(sourceMappingLocalPath, updatedContent);
183 }
184
185 /**
186 * Changes the port of the url to be the one configured as this.packagerPort
187 */
188 private overridePackagerPort(urlToOverride: string): string {
189 let components = url.parse(urlToOverride);
190 components.port = this.packagerPort.toString();
191 delete components.host; // We delete the host, if not the port change will be ignored
192 return url.format(components);
193 }
194}
195