microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3c013cde4fe612455943c1ec04edd32268a040b1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/packager.ts

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