microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f521686613075eb4a9cebc89a01cbcf289ec258f

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

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