microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e54a23cec8f8d2b2adefd558262187fc8444f2c1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

158lines · 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 fs = require("fs");
5import {Log} from "../utils/commands/log";
6import path = require("path");
7import Q = require("q");
8import {Request} from "../utils/node/request";
9import url = require("url");
10
11interface ISourceMap {
12 file: string;
13 sources: string[];
14 version: number;
15 names: string[];
16 mappings: string;
17 sourceRoot?: string;
18 sourcesContent?: string[];
19}
20
21interface DownloadedScript {
22 contents: string;
23 filepath: string;
24}
25
26export class ScriptImporter {
27 private sourcesStoragePath: string;
28
29 constructor(sourcesStoragePath: string) {
30 this.sourcesStoragePath = sourcesStoragePath;
31 }
32
33 public download(scriptUrlString: string): Q.Promise<DownloadedScript> {
34
35 // We'll get the source code, and store it locally to have a better debugging experience
36 return new Request().request(scriptUrlString, true).then(scriptBody => {
37 // Extract sourceMappingURL from body
38 let scriptUrl = url.parse(scriptUrlString); // scriptUrl = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true"
39 let sourceMappingUrl = this.getSourceMapURL(scriptUrl, scriptBody); // sourceMappingUrl = "http://localhost:8081/index.ios.map?platform=ios&dev=true"
40
41 let waitForSourceMapping = Q<void>(null);
42 if (sourceMappingUrl) {
43 /* handle source map - request it and store it locally */
44 waitForSourceMapping = this.writeSourceMap(sourceMappingUrl, scriptUrl)
45 .then(() => {
46 scriptBody = this.updateScriptPaths(scriptBody, sourceMappingUrl);
47 });
48 }
49
50 return waitForSourceMapping
51 .then(() => this.writeScript(scriptBody, scriptUrl))
52 .then((scriptFilePath: string) => {
53 Log.logInternalMessage(`Script ${scriptUrlString} downloaded to ${scriptFilePath}`);
54 return { contents: scriptBody, filepath: scriptFilePath };
55 });
56 });
57 }
58
59 /**
60 * Updates source map URLs in the script body.
61 */
62 private updateScriptPaths(scriptBody: string, sourceMappingUrl: url.Url) {
63 // Update the body with the new location of the source map on storage.
64 return scriptBody.replace(/^\/\/# sourceMappingURL=(.*)$/m, "//# sourceMappingURL=" + path.basename(sourceMappingUrl.pathname));
65 }
66
67 /**
68 * Updates paths in souce maps - VS code requires forward slash paths.
69 */
70 private updateSourceMapPaths(sourceMapBody: string, generatedCodeFilePath: string): string {
71 try {
72 let sourceMap = <ISourceMap>JSON.parse(sourceMapBody);
73 sourceMap.sources = sourceMap.sources.map(source => {
74 // Make all paths relative to the location of the source map
75 let relativeSourcePath = path.relative(this.sourcesStoragePath, source);
76 let sourceUrl = relativeSourcePath.replace(/\\/g, "/");
77 return sourceUrl;
78 });
79 // fixedSourceMapBody.sourceRoot = "..";
80 delete sourceMap.sourcesContent;
81 sourceMap.sourceRoot = "";
82 sourceMap.file = generatedCodeFilePath;
83 return JSON.stringify(sourceMap);
84 } catch (exception) {
85 return sourceMapBody;
86 }
87 }
88 /**
89 * Writes the script file to the project temporary location.
90 */
91 private writeScript(scriptBody: string, scriptUrl: url.Url): Q.Promise<String> {
92 return Q.fcall(() => {
93 let scriptFilePath = path.join(this.sourcesStoragePath, scriptUrl.pathname); // scriptFilePath = "$TMPDIR/index.ios.bundle"
94 this.writeTemporaryFileSync(scriptFilePath, scriptBody);
95 // Log.logMessage("Imported script at " + scriptUrl.path + " locally stored on " + scriptFilePath);
96 return scriptFilePath;
97 });
98 }
99
100 /**
101 * Writes the source map file to the project temporary location.
102 */
103 private writeSourceMap(sourceMapUrl: url.Url, scriptUrl: url.Url): Q.Promise<void> {
104 return new Request().request(sourceMapUrl.href, true)
105 .then((sourceMapBody: string) => {
106 let sourceMappingLocalPath = path.join(this.sourcesStoragePath, sourceMapUrl.pathname); // sourceMappingLocalPath = "$TMPDIR/index.ios.map"
107 let scriptFileRelativePath = path.basename(scriptUrl.pathname); // scriptFileRelativePath = "index.ios.bundle"
108 this.writeTemporaryFileSync(sourceMappingLocalPath, this.updateSourceMapPaths(sourceMapBody, scriptFileRelativePath));
109 });
110 }
111
112 /**
113 * Given a script body and URL, this method parses the body and finds the corresponding source map URL.
114 * If the source map URL is not found in the body in the expected form, null is returned.
115 */
116 private getSourceMapURL(scriptUrl: url.Url, scriptBody: string): url.Url {
117 let result: url.Url = null;
118
119 // scriptUrl = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true"
120 let sourceMappingRelativeUrl = this.sourceMapRelativeUrl(scriptBody); // sourceMappingRelativeUrl = "/index.ios.map?platform=ios&dev=true"
121 if (sourceMappingRelativeUrl) {
122 let sourceMappingUrl = url.parse(sourceMappingRelativeUrl);
123 sourceMappingUrl.protocol = scriptUrl.protocol;
124 sourceMappingUrl.host = scriptUrl.host;
125 // parse() repopulates all the properties of the URL
126 result = url.parse(url.format(sourceMappingUrl));
127 }
128
129 return result;
130 }
131
132 /**
133 * Parses the body of a script searching for a source map URL.
134 * Returns the first match if found, null otherwise.
135 */
136 private sourceMapRelativeUrl(body: string) {
137 let match = body.match(/^\/\/# sourceMappingURL=(.*)$/m);
138 // If match is null, the body doesn't contain the source map
139 return match ? match[1] : null;
140 }
141
142 private writeTemporaryFileSync(filename: string, data: string): Q.Promise<void> {
143 let writeFile = Q.nfbind<void>(fs.writeFile);
144
145 return writeFile(filename, data)
146 .then(() => this.scheduleTemporaryFileCleanUp(filename));
147 }
148
149 private scheduleTemporaryFileCleanUp(filename: string): void {
150 process.on("exit", function() {
151 let unlink = Q.nfbind<void>(fs.unlink);
152 unlink(filename)
153 .then(() => {
154 Log.logMessage("Succesfully cleaned temporary file: " + filename);
155 });
156 });
157 }
158}
159