microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
89973b41e393a344a45388d2e4b7318dfd5888d6

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

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