microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d55f3c22ee18a37c605867c8bf588451292bd24e

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

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