microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3d97c2a334a6b16ef7dfc654b8b33e23dac3f01a

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/scriptImporter.ts

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