UNPKG

spinjs

Version:

<p align="center"><a href="#"><img width="150" src="https://rawgit.com/sysgears/jsapp/master/packages/spinjs/logo.svg"></a></p>

1,073 lines (1,072 loc) 54.6 kB
"use strict"; var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var _this = this; Object.defineProperty(exports, "__esModule", { value: true }); var child_process_1 = require("child_process"); var cluster = require("cluster"); var cors = require("connect-cors"); var crypto = require("crypto"); var Debug = require("debug"); var detectPort = require("detect-port"); var fs = require("fs"); var http = require("http"); var humps = require("humps"); var ip = require("ip"); var isDocker = require("is-docker"); var _ = require("lodash"); var minilog = require("minilog"); var mkdirp = require("mkdirp"); var path = require("path"); var serveStatic = require("serve-static"); var source_list_map_1 = require("source-list-map"); var url = require("url"); var webpack_sources_1 = require("webpack-sources"); var liveReloadMiddleware_1 = require("./plugins/react-native/liveReloadMiddleware"); var symbolicateMiddleware_1 = require("./plugins/react-native/symbolicateMiddleware"); var Spin_1 = require("./Spin"); var webpackHooks_1 = require("./webpackHooks"); var SPIN_DLL_VERSION = 2; var BACKEND_CHANGE_MSG = 'backend_change'; var debug = Debug('spinjs'); var expoPorts = {}; var clientStats = { all: false, assets: true, warnings: true, errors: true, errorDetails: false }; var spinLogger = minilog('spin'); process.on('uncaughtException', function (ex) { spinLogger.error(ex); }); process.on('unhandledRejection', function (reason) { spinLogger.error(reason); }); var __WINDOWS__ = /^win/.test(process.platform); var server; var startBackend = false; var nodeDebugOpt; process.on('exit', function () { if (server) { server.kill('SIGTERM'); } }); var spawnServer = function (cwd, args, options, logger) { server = child_process_1.spawn('node', args.slice(), { stdio: [0, 1, 2], cwd: cwd }); logger.debug("Spawning " + ['node'].concat(args).join(' ')); server.on('exit', function (code) { if (code === 250) { startBackend = true; } logger.info('Backend has been stopped'); server = undefined; runServer(cwd, options.serverPath, options.nodeDebugger, logger); }); }; var runServer = function (cwd, serverPath, nodeDebugger, logger) { if (!fs.existsSync(serverPath)) { throw new Error("Backend doesn't exist at " + serverPath + ", exiting"); } if (startBackend) { startBackend = false; logger.debug('Starting backend'); if (!nodeDebugOpt) { if (!nodeDebugger) { spawnServer(cwd, [serverPath], { serverPath: serverPath, nodeDebugger: nodeDebugger }, logger); } else { child_process_1.exec('node -v', function (error, stdout, stderr) { if (error) { spinLogger.error(error); process.exit(1); } var nodeVersion = stdout.match(/^v([0-9]+)\.([0-9]+)\.([0-9]+)/); var nodeMajor = parseInt(nodeVersion[1], 10); var nodeMinor = parseInt(nodeVersion[2], 10); nodeDebugOpt = nodeMajor >= 6 || (nodeMajor === 6 && nodeMinor >= 9) ? '--inspect' : '--debug'; detectPort(9229).then(function (debugPort) { spawnServer(cwd, [nodeDebugOpt + '=' + debugPort, serverPath], { serverPath: serverPath, nodeDebugger: nodeDebugger }, logger); }); }); } } else { spawnServer(cwd, [nodeDebugOpt, serverPath], { serverPath: serverPath, nodeDebugger: nodeDebugger }, logger); } } }; var webpackReporter = function (spin, builder, outputPath, log, err, stats) { if (err) { log.error(err.stack); throw new Error('Build error'); } if (stats) { var str = stats.toString(builder.config.stats); if (str.length > 0) { log.info(str); } if (builder.writeStats) { mkdirp.sync(outputPath); fs.writeFileSync(path.join(outputPath, 'stats.json'), JSON.stringify(stats.toJson(clientStats))); } } if (!spin.watch && cluster.isWorker) { log.info('Build process finished, exitting...'); process.exit(0); } }; var frontendVirtualModules = []; var MobileAssetsPlugin = (function () { function MobileAssetsPlugin(vendorAssets) { this.vendorAssets = vendorAssets || []; } MobileAssetsPlugin.prototype.apply = function (compiler) { var _this = this; webpackHooks_1.hookAsync(compiler, 'after-compile', function (compilation, callback) { compilation.chunks.forEach(function (chunk) { chunk.files.forEach(function (file) { if (file.endsWith('.bundle')) { var assets_1 = _this.vendorAssets; compilation.modules.forEach(function (module) { if (module._asset) { assets_1.push(module._asset); } }); compilation.assets[file.replace('.bundle', '') + '.assets'] = new webpack_sources_1.RawSource(JSON.stringify(assets_1)); } }); }); callback(); }); }; return MobileAssetsPlugin; }()); var startClientWebpack = function (hasBackend, spin, builder) { var webpack = builder.require('webpack'); var config = builder.config; var configOutputPath = config.output.path; var VirtualModules = builder.require('webpack-virtual-modules'); var clientVirtualModules = new VirtualModules({ 'node_modules/backend_reload.js': '' }); config.plugins.push(clientVirtualModules); frontendVirtualModules.push(clientVirtualModules); var logger = minilog(config.name + "-webpack"); if (builder.silent) { logger.suggest.deny(/.*/, 'debug'); } try { var reporter = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return webpackReporter.apply(void 0, [spin, builder, configOutputPath, logger].concat(args)); }; if (spin.watch) { startWebpackDevServer(hasBackend, spin, builder, reporter, logger); } else { if (builder.stack.platform !== 'web') { config.plugins.push(new MobileAssetsPlugin()); } var compiler = webpack(config); compiler.run(reporter); } } catch (err) { logger.error(err.message, err.stack); } }; var backendReloadCount = 0; var increaseBackendReloadCount = function () { backendReloadCount++; for (var _i = 0, frontendVirtualModules_1 = frontendVirtualModules; _i < frontendVirtualModules_1.length; _i++) { var virtualModules = frontendVirtualModules_1[_i]; virtualModules.writeModule('node_modules/backend_reload.js', "var count = " + backendReloadCount + ";\n"); } }; var startServerWebpack = function (spin, builder) { var config = builder.config; var logger = minilog(config.name + "-webpack"); if (builder.silent) { logger.suggest.deny(/.*/, 'debug'); } try { var webpack = builder.require('webpack'); var reporter = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return webpackReporter.apply(void 0, [spin, builder, config.output.path, logger].concat(args)); }; var compiler = webpack(config); if (spin.watch) { webpackHooks_1.hookSync(compiler, 'done', function (stats) { if (stats.compilation.errors && stats.compilation.errors.length) { stats.compilation.errors.forEach(function (error) { return logger.error(error.message); }); } }); webpackHooks_1.hookSync(compiler, 'compilation', function (compilation) { webpackHooks_1.hookSync(compilation, 'after-optimize-assets', function (assets) { var mapKey = _.findKey(assets, function (v, k) { return k.endsWith('.map'); }); if (mapKey) { var srcMap = JSON.parse(assets[mapKey]._value); for (var _i = 0, _a = Object.keys(srcMap.sources); _i < _a.length; _i++) { var idx = _a[_i]; srcMap.sources[idx] = srcMap.sources[idx].split(';')[0]; } assets[mapKey]._value = JSON.stringify(srcMap); } }); }); compiler.watch({}, reporter); webpackHooks_1.hookSync(compiler, 'done', function (stats) { if (!stats.compilation.errors.length) { var output = config.output; startBackend = true; if (server) { if (!__WINDOWS__) { server.kill('SIGUSR2'); } if (builder.frontendRefreshOnBackendChange) { for (var _i = 0, _a = stats.compilation.modules; _i < _a.length; _i++) { var module_1 = _a[_i]; if (module_1.built && module_1.resource && module_1.resource.split(/[\\\/]/).indexOf('server') >= 0) { logger.debug('Force front-end current page refresh, due to change in backend at:', module_1.resource); process.send({ cmd: BACKEND_CHANGE_MSG }); break; } } } } else { runServer(builder.require.cwd, path.join(output.path, 'index.js'), builder.nodeDebugger, logger); } } }); } else { compiler.run(reporter); } } catch (err) { logger.error(err.message, err.stack); } }; var openFrontend = function (spin, builder, logger) { var opn = builder.require('opn'); try { if (builder.stack.hasAny('web')) { var lanUrl = "http://" + ip.address() + ":" + builder.config.devServer.port; var localUrl = "http://localhost:" + builder.config.devServer.port; if (isDocker() || builder.openBrowser === false) { logger.info("App is running at, Local: " + localUrl + " LAN: " + lanUrl); } else { opn(localUrl); } } else if (builder.stack.hasAny('react-native')) { startExpoProject(spin, builder, logger); } } catch (e) { logger.error(e.stack); } }; var debugMiddleware = function (req, res, next) { if (['/debug', '/debug/bundles'].indexOf(req.path) >= 0) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('<!doctype html><div><a href="/debug/bundles">Cached Bundles</a></div>'); } else { next(); } }; var startWebpackDevServer = function (hasBackend, spin, builder, reporter, logger) { var webpack = builder.require('webpack'); var config = builder.config; var platform = builder.stack.platform; var configOutputPath = config.output.path; config.output.path = '/'; var vendorHashesJson; var vendorSourceListMap; var vendorSource; var vendorMap; if (builder.webpackDll && builder.child) { var name = "vendor_" + humps.camelize(builder.name); var jsonPath = path.join(builder.dllBuildDir, name + "_dll.json"); var json = JSON.parse(fs.readFileSync(path.resolve('./' + jsonPath)).toString()); config.plugins.push(new webpack.DllReferencePlugin({ context: process.cwd(), manifest: json })); vendorHashesJson = JSON.parse(fs.readFileSync(path.join(builder.dllBuildDir, name + "_dll_hashes.json")).toString()); vendorSource = new webpack_sources_1.RawSource(fs.readFileSync(path.join(builder.dllBuildDir, vendorHashesJson.name)).toString() + '\n'); if (platform !== 'web') { var vendorAssets = JSON.parse(fs.readFileSync(path.join(builder.dllBuildDir, vendorHashesJson.name + '.assets')).toString()); config.plugins.push(new MobileAssetsPlugin(vendorAssets)); } if (builder.sourceMap) { vendorMap = new webpack_sources_1.RawSource(fs.readFileSync(path.join(builder.dllBuildDir, vendorHashesJson.name + '.map')).toString()); vendorSourceListMap = source_list_map_1.fromStringWithSourceMap(vendorSource.source(), JSON.parse(vendorMap.source())); } } var compiler = webpack(config); var awaitedAlready = false; webpackHooks_1.hookAsync(compiler, 'after-emit', function (compilation, callback) { if (!awaitedAlready) { if (hasBackend || builder.waitOn) { var waitOnUrls_1; var backendOption = builder.backendUrl || builder.backendUrl; if (backendOption) { var _a = url.parse(backendOption.replace('{ip}', ip.address())), protocol = _a.protocol, hostname = _a.hostname, port = _a.port; waitOnUrls_1 = ["tcp:" + hostname + ":" + (port || (protocol === 'https:' ? 443 : 80))]; } else { waitOnUrls_1 = builder.waitOn ? [].concat(builder.waitOn) : undefined; } if (waitOnUrls_1 && waitOnUrls_1.length) { logger.debug("waiting for " + waitOnUrls_1); var waitStart_1 = Date.now(); var waitNotifier_1 = setInterval(function () { logger.debug("still waiting for " + waitOnUrls_1 + " after " + (Date.now() - waitStart_1) + "ms..."); }, 10000); var waitOn = builder.require('wait-on'); waitOn({ resources: waitOnUrls_1 }, function (err) { clearInterval(waitNotifier_1); awaitedAlready = true; if (err) { logger.error(err); } else { logger.debug('Backend has been started, resuming webpack dev server...'); } callback(); }); } else { awaitedAlready = true; callback(); } } else { callback(); } } else { callback(); } }); if (builder.webpackDll && builder.child && platform !== 'web') { webpackHooks_1.hookAsync(compiler, 'after-compile', function (compilation, callback) { compilation.chunks.forEach(function (chunk) { chunk.files.forEach(function (file) { if (file.endsWith('.bundle')) { if (builder.sourceMap) { var sourceListMap = new source_list_map_1.SourceListMap(); sourceListMap.add(vendorSourceListMap); sourceListMap.add(source_list_map_1.fromStringWithSourceMap(compilation.assets[file].source(), JSON.parse(compilation.assets[file + '.map'].source()))); var sourceAndMap = sourceListMap.toStringWithSourceMap({ file: file }); compilation.assets[file] = new webpack_sources_1.RawSource(sourceAndMap.source); compilation.assets[file + '.map'] = new webpack_sources_1.RawSource(JSON.stringify(sourceAndMap.map)); } else { compilation.assets[file] = new webpack_sources_1.ConcatSource(vendorSource, compilation.assets[file]); } } }); }); callback(); }); } if (builder.webpackDll && builder.child && platform === 'web' && !builder.ssr) { webpackHooks_1.hookAsync(compiler, 'after-compile', function (compilation, callback) { compilation.assets[vendorHashesJson.name] = vendorSource; if (builder.sourceMap) { compilation.assets[vendorHashesJson.name + '.map'] = vendorMap; } callback(); }); webpackHooks_1.hookSync(compiler, 'compilation', function (compilation) { webpackHooks_1.hookAsync(compilation, 'html-webpack-plugin-before-html-processing', function (htmlPluginData, callback) { htmlPluginData.assets.js.unshift('/' + vendorHashesJson.name); callback(null, htmlPluginData); }); }); } var frontendFirstStart = true; webpackHooks_1.hookSync(compiler, 'done', function (stats) { var dir = configOutputPath; mkdirp.sync(dir); if (stats.compilation.assets['assets.json']) { var assetsMap_1 = JSON.parse(stats.compilation.assets['assets.json'].source()); var prefix_1 = compiler.outputPath; _.each(stats.toJson(clientStats).assetsByChunkName, function (assets, bundle) { var bundleJs = assets.constructor === Array ? assets[0] : assets; assetsMap_1[bundle + ".js"] = prefix_1 + bundleJs; if (assets.length > 1) { assetsMap_1[bundle + ".js.map"] = prefix_1 + (bundleJs + ".map"); } }); if (builder.webpackDll) { assetsMap_1['vendor.js'] = prefix_1 + vendorHashesJson.name; } fs.writeFileSync(path.join(dir, 'assets.json'), JSON.stringify(assetsMap_1)); } if (frontendFirstStart) { frontendFirstStart = false; openFrontend(spin, builder, logger); } }); var serverInstance; var webSocketProxy; var messageSocket; var wsProxy; var ms; var inspectorProxy; if (platform === 'web') { var WebpackDevServer = builder.require('webpack-dev-server'); serverInstance = new WebpackDevServer(compiler, __assign({}, config.devServer, { reporter: function (opts1, opts2) { var opts = opts2 || opts1; var state = opts.state, stats = opts.stats; if (state) { logger.debug('bundle is now VALID.'); } else { logger.debug('bundle is now INVALID.'); } reporter(null, stats); } })); } else { var connect = builder.require('connect'); var compression = builder.require('compression'); var httpProxyMiddleware_1 = builder.require('http-proxy-middleware'); var mime = builder.require('mime', builder.require.resolve('webpack-dev-middleware')); var webpackDevMiddleware = builder.require('webpack-dev-middleware'); var webpackHotMiddleware = builder.require('webpack-hot-middleware'); var app_1 = connect(); serverInstance = http.createServer(app_1); mime.define({ 'application/javascript': ['bundle'] }, true); mime.define({ 'application/json': ['assets'] }, true); messageSocket = builder.require('react-native/local-cli/server/util/messageSocket.js'); webSocketProxy = builder.require('react-native/local-cli/server/util/webSocketProxy.js'); try { var InspectorProxy = builder.require('react-native/local-cli/server/util/inspectorProxy.js'); inspectorProxy = new InspectorProxy(); } catch (ignored) { } var copyToClipBoardMiddleware = builder.require('react-native/local-cli/server/middleware/copyToClipBoardMiddleware'); var cpuProfilerMiddleware = void 0; try { cpuProfilerMiddleware = builder.require('react-native/local-cli/server/middleware/cpuProfilerMiddleware'); } catch (ignored) { } var getDevToolsMiddleware = builder.require('react-native/local-cli/server/middleware/getDevToolsMiddleware'); var heapCaptureMiddleware = void 0; try { heapCaptureMiddleware = builder.require('react-native/local-cli/server/middleware/heapCaptureMiddleware.js'); } catch (ignored) { } var indexPageMiddleware = builder.require('react-native/local-cli/server/middleware/indexPage'); var loadRawBodyMiddleware = builder.require('react-native/local-cli/server/middleware/loadRawBodyMiddleware'); var openStackFrameInEditorMiddleware = builder.require('react-native/local-cli/server/middleware/openStackFrameInEditorMiddleware'); var statusPageMiddleware = builder.require('react-native/local-cli/server/middleware/statusPageMiddleware.js'); var systraceProfileMiddleware = builder.require('react-native/local-cli/server/middleware/systraceProfileMiddleware.js'); var unless = builder.require('react-native/local-cli/server/middleware/unless'); compiler.options.output.path = path.sep; var devMiddleware_1 = webpackDevMiddleware(compiler, _.merge({}, config.devServer, { reporter: function (mwOpts, _a) { var state = _a.state, stats = _a.stats; if (state) { logger.info('bundle is now VALID.'); } else { logger.info('bundle is now INVALID.'); } reporter(null, stats); } })); var args = { port: config.devServer.port, projectRoots: [path.resolve('.')] }; app_1 .use(cors()) .use(loadRawBodyMiddleware) .use(function (req, res, next) { req.path = req.url.split('?')[0]; if (req.path === '/symbolicate') { req.rawBody = req.rawBody.replace(/index\.mobile\.delta/g, 'index.mobile.bundle'); } var origWriteHead = res.writeHead; res.writeHead = function () { var parms = []; for (var _i = 0; _i < arguments.length; _i++) { parms[_i] = arguments[_i]; } var code = parms[0]; if (code === 404) { logger.error("404 at URL " + req.url); } origWriteHead.apply(res, parms); }; if (debug.enabled && req.path !== '/onchange') { logger.debug("Dev mobile packager request: " + (debug.enabled ? req.url : req.path)); } next(); }) .use(function (req, res, next) { var query = url.parse(req.url, true).query; var urlPlatform = query && query.platform; if (urlPlatform && urlPlatform !== builder.stack.platform) { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end("Serving '" + builder.stack.platform + "' bundles, but got request from '" + urlPlatform + "'"); } else { next(); } }) .use(compression()); app_1.use('/assets', serveStatic(path.join(builder.require.cwd, '.expo', builder.stack.platform))); if (builder.child) { app_1.use(serveStatic(builder.child.config.output.path)); } app_1 .use(function (req, res, next) { if (req.path === '/debugger-ui/deltaUrlToBlobUrl.js') { debug("serving monkey patched deltaUrlToBlobUrl"); res.writeHead(200, { 'Content-Type': 'application/javascript' }); res.end("window.deltaUrlToBlobUrl = function(url) { return url.replace('.delta', '.bundle'); }"); } else { next(); } }) .use('/debugger-ui', serveStatic(path.join(path.dirname(builder.require.resolve('react-native/package.json')), '/local-cli/server/util/debugger-ui'))) .use(getDevToolsMiddleware(args, function () { return wsProxy && wsProxy.isChromeConnected(); })) .use(getDevToolsMiddleware(args, function () { return ms && ms.isChromeConnected(); })) .use(liveReloadMiddleware_1.default(compiler)) .use(symbolicateMiddleware_1.default(compiler, logger)) .use(openStackFrameInEditorMiddleware(args)) .use(copyToClipBoardMiddleware) .use(statusPageMiddleware) .use(systraceProfileMiddleware) .use(indexPageMiddleware) .use(debugMiddleware); if (heapCaptureMiddleware) { app_1.use(heapCaptureMiddleware); } if (cpuProfilerMiddleware) { app_1.use(cpuProfilerMiddleware); } if (inspectorProxy) { app_1.use(unless('/inspector', inspectorProxy.processRequest.bind(inspectorProxy))); } app_1 .use(function (req, res, next) { if (platform !== 'web') { var origSetHeader_1 = res.setHeader; res.setHeader = function (key, value) { var val = value; if (key === 'Content-Type' && value.indexOf('application/javascript') >= 0) { val = value.split(';')[0]; } origSetHeader_1.call(res, key, val); }; } return devMiddleware_1(req, res, next); }) .use(webpackHotMiddleware(compiler, { log: false })); if (config.devServer.proxy) { Object.keys(config.devServer.proxy).forEach(function (key) { app_1.use(httpProxyMiddleware_1(key, config.devServer.proxy[key])); }); } } logger.info("Webpack dev server listening on http://localhost:" + config.devServer.port); serverInstance.listen(config.devServer.port, function () { if (platform !== 'web') { wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy'); ms = messageSocket.attachToServer(serverInstance, '/message'); webSocketProxy.attachToServer(serverInstance, '/devtools'); if (inspectorProxy) { inspectorProxy.attachToServer(serverInstance, '/inspector'); } } }); serverInstance.timeout = 0; serverInstance.keepAliveTimeout = 0; }; var isDllValid = function (spin, builder, logger) { var name = "vendor_" + humps.camelize(builder.name); try { var hashesPath = path.join(builder.dllBuildDir, name + "_dll_hashes.json"); if (!fs.existsSync(hashesPath)) { return false; } var relMeta = JSON.parse(fs.readFileSync(hashesPath).toString()); if (SPIN_DLL_VERSION !== relMeta.version) { return false; } if (!fs.existsSync(path.join(builder.dllBuildDir, relMeta.name))) { return false; } if (builder.sourceMap && !fs.existsSync(path.join(builder.dllBuildDir, relMeta.name + '.map'))) { return false; } if (!_.isEqual(relMeta.modules, builder.child.config.entry.vendor)) { return false; } var json = JSON.parse(fs.readFileSync(path.join(builder.dllBuildDir, name + "_dll.json")).toString()); for (var _i = 0, _a = Object.keys(json.content); _i < _a.length; _i++) { var filename = _a[_i]; if (filename.indexOf(' ') < 0 && filename.indexOf('@virtual') < 0) { if (!fs.existsSync(filename)) { logger.warn(name + " DLL need to be regenerated, file: " + filename + " is missing."); return false; } var hash = crypto .createHash('md5') .update(fs.readFileSync(filename)) .digest('hex'); if (relMeta.hashes[filename] !== hash) { logger.warn("Hash for " + name + " DLL file " + filename + " has changed, need to rebuild it"); return false; } } } return true; } catch (e) { logger.warn("Error checking vendor bundle " + name + ", regenerating it...", e); return false; } }; var buildDll = function (spin, builder) { var webpack = builder.require('webpack'); var config = builder.child.config; return new Promise(function (done) { var name = "vendor_" + humps.camelize(builder.name); var logger = minilog(config.name + "-webpack"); if (builder.silent) { logger.suggest.deny(/.*/, 'debug'); } var reporter = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return webpackReporter.apply(void 0, [spin, builder, config.output.path, logger].concat(args)); }; if (!isDllValid(spin, builder, logger)) { logger.debug("Generating " + name + " DLL bundle with modules:\n" + JSON.stringify(config.entry.vendor)); mkdirp.sync(builder.dllBuildDir); var compiler = webpack(config); webpackHooks_1.hookSync(compiler, 'done', function (stats) { try { var json = JSON.parse(fs.readFileSync(path.join(builder.dllBuildDir, name + "_dll.json")).toString()); var vendorKey = _.findKey(stats.compilation.assets, function (v, key) { return key.startsWith('vendor') && key.endsWith('_dll.js'); }); var assets_2 = []; stats.compilation.modules.forEach(function (module) { if (module._asset) { assets_2.push(module._asset); } }); fs.writeFileSync(path.join(builder.dllBuildDir, vendorKey + ".assets"), JSON.stringify(assets_2)); var meta = { name: vendorKey, hashes: {}, modules: config.entry.vendor, version: SPIN_DLL_VERSION }; for (var _i = 0, _a = Object.keys(json.content); _i < _a.length; _i++) { var filename = _a[_i]; if (filename.indexOf(' ') < 0 && filename.indexOf('@virtual') < 0) { meta.hashes[filename] = crypto .createHash('md5') .update(fs.readFileSync(filename)) .digest('hex'); } } fs.writeFileSync(path.join(builder.dllBuildDir, name + "_dll_hashes.json"), JSON.stringify(meta)); fs.writeFileSync(path.join(builder.dllBuildDir, name + "_dll.json"), JSON.stringify(json)); } catch (e) { logger.error(e.stack); process.exit(1); } done(); }); compiler.run(reporter); } else { done(); } }); }; var copyExpoImage = function (cwd, expoDir, appJson, keyPath) { var imagePath = _.get(appJson, keyPath); if (imagePath) { var absImagePath = path.join(cwd, imagePath); fs.writeFileSync(path.join(expoDir, path.basename(absImagePath)), fs.readFileSync(absImagePath)); _.set(appJson, keyPath, path.basename(absImagePath)); } }; var setupExpoDir = function (spin, builder, dir, platform) { var reactNativeDir = path.join(dir, 'node_modules', 'react-native'); mkdirp.sync(path.join(reactNativeDir, 'local-cli')); fs.writeFileSync(path.join(reactNativeDir, 'package.json'), fs.readFileSync(builder.require.resolve('react-native/package.json'))); fs.writeFileSync(path.join(reactNativeDir, 'local-cli/cli.js'), ''); var reactDir = path.join(dir, 'node_modules', 'react'); mkdirp.sync(reactDir); fs.writeFileSync(path.join(reactDir, 'package.json'), fs.readFileSync(builder.require.resolve('react/package.json'))); var pkg = JSON.parse(fs.readFileSync(builder.require.resolve('./package.json')).toString()); var origDeps = pkg.dependencies; delete pkg.devDependencies; pkg.dependencies = { react: origDeps.react, 'react-native': origDeps['react-native'] }; if (platform !== 'all') { pkg.name = pkg.name + '-' + platform; } pkg.main = "index.mobile"; fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2)); var appJson = JSON.parse(fs.readFileSync(builder.require.resolve('./app.json')).toString()); [ 'expo.icon', 'expo.ios.icon', 'expo.android.icon', 'expo.splash.image', 'expo.ios.splash.image', 'expo.ios.splash.tabletImage', 'expo.android.splash.ldpi', 'expo.android.splash.mdpi', 'expo.android.splash.hdpi', 'expo.android.splash.xhdpi', 'expo.android.splash.xxhdpi', 'expo.android.splash.xxxhdpi' ].forEach(function (keyPath) { return copyExpoImage(builder.require.cwd, dir, appJson, keyPath); }); fs.writeFileSync(path.join(dir, 'app.json'), JSON.stringify(appJson, null, 2)); if (platform !== 'all') { fs.writeFileSync(path.join(dir, '.exprc'), JSON.stringify({ manifestPort: expoPorts[platform] }, null, 2)); } }; var deviceLoggers = {}; var mirrorExpoLogs = function (builder, projectRoot) { var ProjectUtils = builder.require('xdl').ProjectUtils; deviceLoggers[projectRoot] = minilog('expo-for-' + builder.name); if (!ProjectUtils.logWithLevel._patched) { var origExpoLogger_1 = ProjectUtils.logWithLevel; ProjectUtils.logWithLevel = function (projRoot, level, object, msg, id) { var json; if (msg[0] === '{') { json = JSON.parse(msg); } if (level === 'error') { var info = object.includesStack ? json.message + '\n' + json.stack : json.message; deviceLoggers[projRoot].log(info.replace(/\\n/g, '\n')); } else if (json) { deviceLoggers[projRoot].log(msg.message); } else { deviceLoggers[projRoot].log(msg); } return origExpoLogger_1.call(ProjectUtils, projRoot, level, object, msg, id); }; ProjectUtils.logWithLevel._patched = true; } }; var startExpoServer = function (spin, builder, projectRoot, packagerPort) { return __awaiter(_this, void 0, void 0, function () { var _a, Config, Project, ProjectSettings; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = builder.require('xdl'), Config = _a.Config, Project = _a.Project, ProjectSettings = _a.ProjectSettings; mirrorExpoLogs(builder, projectRoot); Config.validation.reactNativeVersionWarnings = false; Config.developerTool = 'crna'; Config.offline = true; return [4, Project.startExpoServerAsync(projectRoot)]; case 1: _b.sent(); return [4, ProjectSettings.setPackagerInfoAsync(projectRoot, { packagerPort: packagerPort })]; case 2: _b.sent(); return [2]; } }); }); }; var startExpoProject = function (spin, builder, logger) { return __awaiter(_this, void 0, void 0, function () { var _a, UrlUtils, Android, Simulator, qr, platform, projectRoot, address, localAddress, _b, success, error, _c, success, msg, e_1; return __generator(this, function (_d) { switch (_d.label) { case 0: _a = builder.require('xdl'), UrlUtils = _a.UrlUtils, Android = _a.Android, Simulator = _a.Simulator; qr = builder.require('qrcode-terminal'); platform = builder.stack.platform; _d.label = 1; case 1: _d.trys.push([1, 9, , 10]); projectRoot = path.join(builder.require.cwd, '.expo', platform); setupExpoDir(spin, builder, projectRoot, platform); return [4, startExpoServer(spin, builder, projectRoot, builder.config.devServer.port)]; case 2: _d.sent(); return [4, UrlUtils.constructManifestUrlAsync(projectRoot)]; case 3: address = _d.sent(); return [4, UrlUtils.constructManifestUrlAsync(projectRoot, { hostType: 'localhost' })]; case 4: localAddress = _d.sent(); logger.info("Expo address for " + platform + ", Local: " + localAddress + ", LAN: " + address); logger.info("To open this app on your phone scan this QR code in Expo Client (if it doesn't get started automatically)"); qr.generate(address, function (code) { logger.info('\n' + code); }); if (!!isDocker()) return [3, 8]; if (!(platform === 'android')) return [3, 6]; return [4, Android.openProjectAsync(projectRoot)]; case 5: _b = _d.sent(), success = _b.success, error = _b.error; if (!success) { logger.error(error.message); } return [3, 8]; case 6: if (!(platform === 'ios')) return [3, 8]; return [4, Simulator.openUrlInSimulatorSafeAsync(localAddress)]; case 7: _c = _d.sent(), success = _c.success, msg = _c.msg; if (!success) { logger.error('Failed to start Simulator: ', msg); } _d.label = 8; case 8: return [3, 10]; case 9: e_1 = _d.sent(); logger.error(e_1.stack); return [3, 10]; case 10: return [2]; } }); }); }; var startWebpack = function (spin, builder, platforms) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { if (builder.stack.platform === 'server') { startServerWebpack(spin, builder); } else { startClientWebpack(!!platforms.server, spin, builder); } return [2]; }); }); }; var allocateExpoPorts = function (expoPlatforms) { return __awaiter(_this, void 0, void 0, function () { var startPorts, _i, expoPlatforms_1, platform, expoPort; return __generator(this, function (_a) { switch (_a.label) { case 0: startPorts = { android: 19000, ios: 19500 }; _i = 0, expoPlatforms_1 = expoPlatforms; _a.label = 1; case 1: if (!(_i < expoPlatforms_1.length)) return [3, 4]; platform = expoPlatforms_1[_i]; return [4, detectPort(startPorts[platform])]; case 2: expoPort = _a.sent(); expoPorts[platform] = expoPort; _a.label = 3; case 3: _i++; return [3, 1]; case 4: return [2]; } }); }); }; var startExpoProdServer = function (spin, mainBuilder, builders, logger) { return __awaiter(_this, void 0, void 0, function () { var connect, mime, compression, statusPageMiddleware, UrlUtils, packagerPort, app, serverInstance, projectRoot, localAddress; return __generator(this, function (_a) { switch (_a.label) { case 0: connect = mainBuilder.require('connect'); mime = mainBuilder.require('mime', mainBuilder.require.resolve('webpack-dev-middleware')); compression = mainBuilder.require('compression'); statusPageMiddleware = mainBuilder.require('react-native/local-cli/server/middleware/statusPageMiddleware.js'); UrlUtils = mainBuilder.require('xdl').UrlUtils; logger.info("Starting Expo prod server"); packagerPort = 3030; app = connect(); app .use(function (req, res, next) { req.path = req.url.split('?')[0]; debug("Prod mobile packager request: " + req.url); next(); }) .use(statusPageMiddleware) .use(compression()) .use(debugMiddleware) .use(function (req, res, next) { var platform = url.parse(req.url, true).query.platform; if (platform) { var platformFound = false; for (var _i = 0, _a = Object.keys(builders); _i < _a.length; _i++) { var name = _a[_i]; var builder = builders[name]; if (builder.stack.hasAny(platform)) { platformFound = true; var filePath = builder.buildDir ? path.join(builder.buildDir, req.path) : path.join(builder.frontendBuildDir || "build/client", platform, req.path); if (fs.existsSync(filePath)) { res.writeHead(200, { 'Content-Type': mime.lookup ? mime.lookup(filePath) : mime.getType(filePath) }); fs.createReadStream(filePath).pipe(res); return; } } } if (!platformFound) { logger.error("Bundle for '" + platform + "' platform is missing! You need to build bundles both for Android and iOS."); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end("{\"message\": \"File not found for request: " + req.path + "\"}"); } } else { next(); } }); serverInstance = http.createServer(app); return [4, new Promise(function (resolve, reject) { serverInstance.listen(packagerPort, function () { logger.info("Production mobile packager listening on http://localhost:" + packagerPort); resolve(); }); })]; case 1: _a.sent(); serverInstance.timeout = 0; serverInstance.keepAliveTimeout = 0; projectRoot = path.join(path.resolve('.'), '.expo', 'all'); return [4, startExpoServer(spin, mainBuilder, projectRoot, packagerPort)]; case 2: _a.sent(); return [4, UrlUtils.constructManifestUrlAsync(projectRoot, { hostType: 'localhost' })]; case 3: localAddress = _a.sent(); logger.info("Expo server running on address: " + localAddress); return [2]; } }); }); }; var startExp = function (spin, builders, logger) { return __awaiter(_this, void 0, void 0, function () { var mainBuilder, _i, _a, name, builder, projectRoot, expIdx, exp; return __generator(this, function (_b) { switch (_b.label) { case 0: for (_i = 0, _a = Object.keys(builders); _i < _a.length; _i++) { name = _a[_i]; builder = builders[name]; if (builder.stack.hasAny(['ios', 'android'])) { mainBuilder = builder; break; } } if (!mainBuilder) { throw new Error('Builders for `ios` or `android` not found'); } projectRoot = path.join(process.cwd(), '.expo', 'all'); setupExpoDir(spin, mainBuilder, projectRoot, 'all'); expIdx = process.argv.indexOf('exp'); if (!(['ba', 'bi', 'build:android', 'build:ios', 'publish', 'p', 'server'].indexOf(process.argv[expIdx + 1]) >= 0)) return [3, 2]; return [4, startExpoProdServer(spin, mainBuilder, builders, logger)]; case 1: _b.sent(); _b.label = 2; case 2: if (process.argv[expIdx + 1] !== 'server') { exp = child_process_1.spawn(path.join(process.cwd(), 'node_modules/.bin/exp' + (__WINDOWS__ ? '.cmd' : '')), process.argv.splice(expIdx + 1), { cwd: projectRoot, stdio: [0, 1, 2] }); exp.on('exit', function (code) { process.exit(code); }); } return [2]; } }); }); }; var runBuilder = function (cmd, builder, platforms) { process.chdir(builder.require.cwd); var spin = new Spin_1.default(builder.require.cwd, cmd); var prepareDllPromise = spin.watch && builder.webpackDll && builder.child ? buildDll(spin, builder) : Promise.resolve(); prepareDllPromise.then(function () { return startWebpack(spin, builder, platforms); }); }; var execute = function (cmd, argv, builders, spin) { var expoPlatforms = []; var platforms = {}; Object.keys(builders).forEach(function (name) { var builder = builders[name]; var stack = builder.stack; platforms[stack.platform] = true; if (stack.hasAny('react-native') && stack.hasAny('ios')) { expoPlatforms.push('ios'); } else if (stack.hasAny('react-native') && stack.hasAny('android')) { expoPlatforms.push('android'); } }); if (cluster.isMaster) { if (argv.verbose) { Object.keys(builders).forEach(function (name) { var builder = builders[name]; spinLogger.log(name + " = ", require('util').inspect(builder.config, false, null)); }); } if (cmd === 'exp') { startExp(spin, builders, spinLogger); } else if (cmd === 'test') { var builder = void 0; for (var _i = 0, _a = Object.keys(builders); _i < _a.length; _i++) {