microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-zhenyuan/update-parameters

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

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