microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
565e75ea9338b2607cf546d95b0e36913075068f

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/packager.ts

156lines · 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 {ChildProcess} from "child_process";
5import {CommandExecutor} from "./commandExecutor";
6import {ErrorHelper} from "./error/errorHelper";
7import {Log} from "./log/log";
8import {LogLevel} from "./log/logHelper";
9import {Node} from "./node/node";
10import {OutputChannel} from "vscode";
11import {Package} from "./node/package";
12import {PromiseUtil} from "./node/promise";
13import {Request} from "./node/request";
14
15import * as Q from "q";
16import * as path from "path";
17
18export class Packager {
19 // TODO: Make the port configurable via a launch argument
20 public static PORT = "8081";
21 public static HOST = `localhost:${Packager.PORT}`;
22
23 private projectPath: string;
24 private packagerProcess: ChildProcess;
25
26 private static JS_INJECTOR_FILENAME = "opn-main.js";
27 private static JS_INJECTOR_FILEPATH = path.resolve(path.dirname(path.dirname(__dirname)), "js-patched", Packager.JS_INJECTOR_FILENAME);
28 private static NODE_MODULES_FODLER_NAME = "node_modules";
29 private static OPN_PACKAGE_NAME = "opn";
30 private static REACT_NATIVE_PACKAGE_NAME = "react-native";
31 private static OPN_PACKAGE_MAIN_FILENAME = "index.js";
32
33 constructor(projectPath: string) {
34 this.projectPath = projectPath;
35 }
36
37
38 public start(outputChannel?: OutputChannel): Q.Promise<void> {
39 let executedStartPackagerCmd = false;
40 let targetLogChannel = outputChannel || console;
41 return this.isRunning()
42 .then(running => {
43 if (!running) {
44 return this.monkeyPatchOpnForRNPackager()
45 .then(() => {
46 let args = ["--port", Packager.PORT];
47 let childEnvForDebugging = Object.assign({}, process.env, { REACT_DEBUGGER: "echo A debugger is not needed: " });
48
49 Log.logMessage("Starting Packager", targetLogChannel);
50 // The packager will continue running while we debug the application, so we can"t
51 // wait for this command to finish
52
53 let spawnOptions = { env: childEnvForDebugging };
54
55 // TODO #83 - PROMISE: We need to consume the result of this spawn
56 new CommandExecutor(this.projectPath).spawnReactPackager(args, spawnOptions, outputChannel).then((packagerProcess) => {
57 this.packagerProcess = packagerProcess;
58 executedStartPackagerCmd = true;
59 });
60 });
61 }
62 })
63 .then(() =>
64 this.awaitStart())
65 .then(() => {
66 if (executedStartPackagerCmd) {
67 Log.logMessage("Packager started.", targetLogChannel);
68 } else {
69 Log.logMessage("Packager is already running.", targetLogChannel);
70 if (!outputChannel) {
71 // TODO #83: This warning is printted incorrectly when the packager was started from the command palette. Fix it.
72 Log.logWarning(ErrorHelper.getWarning("Debugging is not supported if the React Native Packager is not started within VS Code. If debugging fails, please kill other active React Native packager processes and retry."), outputChannel);
73 }
74 }
75 });
76 }
77
78 public stop(outputChannel?: OutputChannel): Q.Promise<void> {
79 return new CommandExecutor(this.projectPath).killReactPackager(this.packagerProcess, outputChannel).then(() =>
80 this.packagerProcess = null);
81 }
82
83 public prewarmBundleCache(platform: string) {
84 let bundleURL = `http://${Packager.HOST}/index.${platform}.bundle`;
85 Log.logInternalMessage(LogLevel.Info, "About to get: " + bundleURL);
86 return new Request().request(bundleURL, true).then(() => {
87 Log.logMessage("The Bundle Cache was prewarmed.", [console]);
88 }).catch(() => {
89 // The attempt to prefetch the bundle failed.
90 // This may be because the bundle is not index.* so we shouldn't treat this as fatal.
91 });
92 }
93
94 private isRunning(): Q.Promise<boolean> {
95 let statusURL = `http://${Packager.HOST}/status`;
96
97 return new Request().request(statusURL)
98 .then((body: string) => {
99 return body === "packager-status:running";
100 },
101 (error: any) => {
102 return false;
103 });
104 }
105
106 private awaitStart(retryCount = 30, delay = 2000): Q.Promise<boolean> {
107 let pu: PromiseUtil = new PromiseUtil();
108 return pu.retryAsync(() => this.isRunning(), (running) => running, retryCount, delay, "Could not start the packager.");
109 }
110
111 private findOpnPackage(): Q.Promise<string> {
112 try {
113 let flatDependencyPackagePath = path.resolve(this.projectPath, Packager.NODE_MODULES_FODLER_NAME,
114 Packager.OPN_PACKAGE_NAME, Packager.OPN_PACKAGE_MAIN_FILENAME);
115
116 let nestedDependencyPackagePath = path.resolve(this.projectPath, Packager.NODE_MODULES_FODLER_NAME,
117 Packager.REACT_NATIVE_PACKAGE_NAME, Packager.NODE_MODULES_FODLER_NAME, Packager.OPN_PACKAGE_NAME, Packager.OPN_PACKAGE_MAIN_FILENAME);
118
119 let fsHelper = new Node.FileSystem();
120
121 // Attempt to find the 'opn' package directly under the project's node_modules folder (node4 +)
122 // Else, attempt to find the package within the dependent node_modules of react-native package
123 let possiblePaths = [flatDependencyPackagePath, nestedDependencyPackagePath];
124 return Q.any(possiblePaths.map(path =>
125 fsHelper.exists(path).then(exists =>
126 exists
127 ? Q.resolve(path)
128 : Q.reject<string>("opn package location not found"))));
129 } catch (err) {
130 console.error("The package \'opn\' was not found." + err);
131 }
132 }
133
134 private monkeyPatchOpnForRNPackager(): Q.Promise<void> {
135 let opnPackage: Package;
136 let destnFilePath: string;
137
138 // Finds the 'opn' package
139 return this.findOpnPackage()
140 .then((opnIndexFilePath) => {
141 destnFilePath = opnIndexFilePath;
142 // Read the package's "package.json"
143 opnPackage = new Package(path.resolve(path.dirname(destnFilePath)));
144 return opnPackage.parsePackageInformation();
145 }).then((packageJson) => {
146 if (packageJson.main !== Packager.JS_INJECTOR_FILENAME) {
147 // Copy over the patched 'opn' main file
148 return new Node.FileSystem().copyFile(Packager.JS_INJECTOR_FILEPATH, path.resolve(path.dirname(destnFilePath), Packager.JS_INJECTOR_FILENAME))
149 .then(() => {
150 // Write/over-write the "main" attribute with the new file
151 return opnPackage.setMainFile(Packager.JS_INJECTOR_FILENAME);
152 });
153 }
154 });
155 }
156}