"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.broadcastMessage = broadcastMessage; exports.bundleAsync = bundleAsync; exports.getUrlAsync = getUrlAsync; exports.isTargetingNative = isTargetingNative; exports.openAsync = openAsync; exports.startAsync = startAsync; exports.stopAsync = stopAsync; function _devServer() { const data = require("@expo/dev-server"); _devServer = function () { return data; }; return data; } function _symbolicateMiddleware() { const data = require("@expo/dev-server/build/webpack/symbolicateMiddleware"); _symbolicateMiddleware = function () { return data; }; return data; } function devcert() { const data = _interopRequireWildcard(require("@expo/devcert")); devcert = function () { return data; }; return data; } function _betterOpn() { const data = _interopRequireDefault(require("better-opn")); _betterOpn = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _fsExtra() { const data = _interopRequireDefault(require("fs-extra")); _fsExtra = function () { return data; }; return data; } function _getenv() { const data = _interopRequireDefault(require("getenv")); _getenv = function () { return data; }; return data; } function path() { const data = _interopRequireWildcard(require("path")); path = function () { return data; }; return data; } function _formatWebpackMessages() { const data = _interopRequireDefault(require("react-dev-utils/formatWebpackMessages")); _formatWebpackMessages = function () { return data; }; return data; } function _webpack() { const data = _interopRequireDefault(require("webpack")); _webpack = function () { return data; }; return data; } function _webpackDevServer() { const data = _interopRequireDefault(require("webpack-dev-server")); _webpackDevServer = function () { return data; }; return data; } function _internal() { const data = require("./internal"); _internal = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const WEBPACK_LOG_TAG = 'expo'; let webpackDevServerInstance = null; let webpackServerPort = null; // A custom message websocket broadcaster used to send messages to a React Native runtime. let customMessageSocketBroadcaster; async function clearWebCacheAsync(projectRoot, mode) { const cacheFolder = path().join(projectRoot, '.expo', 'web', 'cache', mode); _internal().ProjectUtils.logInfo(projectRoot, WEBPACK_LOG_TAG, _chalk().default.dim(`Clearing ${mode} cache directory...`)); try { await _fsExtra().default.remove(cacheFolder); } catch {} } // Temporary hack while we implement multi-bundler dev server proxy. const _isTargetingNative = ['ios', 'android'].includes(process.env.EXPO_WEBPACK_PLATFORM || ''); function isTargetingNative() { return _isTargetingNative; } async function broadcastMessage(message, data) { if (!webpackDevServerInstance || !(webpackDevServerInstance instanceof _webpackDevServer().default)) { return; } // Allow any message on native if (customMessageSocketBroadcaster) { customMessageSocketBroadcaster(message, data); return; } if (message !== 'reload') { // TODO: // Webpack currently only supports reloading the client (browser), // remove this when we have custom sockets, and native support. return; } // TODO: // Default webpack-dev-server sockets use "content-changed" instead of "reload" (what we use on native). // For now, just manually convert the value so our CLI interface can be unified. const hackyConvertedMessage = message === 'reload' ? 'content-changed' : message; webpackDevServerInstance.sockWrite(webpackDevServerInstance.sockets, hackyConvertedMessage, data); } function createNativeDevServerMiddleware(projectRoot, { port, compiler, forceManifestType }) { if (!isTargetingNative()) { return null; } const nativeMiddleware = (0, _devServer().createDevServerMiddleware)(projectRoot, { logger: _internal().ProjectUtils.getLogger(projectRoot), port, watchFolders: [projectRoot] }); // Add manifest middleware to the other middleware. // TODO: Move this in to expo/dev-server. const useExpoUpdatesManifest = forceManifestType === 'expo-updates'; const middleware = useExpoUpdatesManifest ? _internal().ExpoUpdatesManifestHandler.getManifestHandler(projectRoot) : _internal().ManifestHandler.getManifestHandler(projectRoot); nativeMiddleware.middleware.use(middleware).use('/symbolicate', (0, _symbolicateMiddleware().createSymbolicateMiddleware)({ projectRoot, compiler, logger: nativeMiddleware.logger })); return nativeMiddleware; } function attachNativeDevServerMiddlewareToDevServer(projectRoot, { server, middleware, logger, // Expo SDK 44 and lower attachToServer, // React Native +68 -- Expo SDK 45 and higher messageSocketEndpoint, eventsSocketEndpoint }) { if (attachToServer) { // Hook up the React Native WebSockets to the Webpack dev server. const { messageSocket, eventsSocket } = attachToServer(server); customMessageSocketBroadcaster = messageSocket.broadcast; const logReporter = new (_devServer().LogReporter)(logger); logReporter.reportEvent = eventsSocket.reportEvent; (0, _devServer().attachInspectorProxy)(projectRoot, { middleware, server }); } else { // React Native +68 const logReporter = new (_devServer().LogReporter)(logger); logReporter.reportEvent = eventsSocketEndpoint.reportEvent; customMessageSocketBroadcaster = messageSocketEndpoint.broadcast; (0, _devServer().attachInspectorProxy)(projectRoot, { middleware, server }); } } async function startAsync(projectRoot, options = {}) { var _config$devServer; await stopAsync(projectRoot); if (webpackDevServerInstance) { _internal().ProjectUtils.logError(projectRoot, WEBPACK_LOG_TAG, _chalk().default.red(`Webpack is already running.`)); return null; } const fullOptions = transformCLIOptions(options); const env = await getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, fullOptions); if (fullOptions.clear) { await clearWebCacheAsync(projectRoot, env.mode); } if (env.https) { if (!process.env.SSL_CRT_FILE || !process.env.SSL_KEY_FILE) { const ssl = await getSSLCertAsync({ name: 'localhost', directory: projectRoot }); if (ssl) { process.env.SSL_CRT_FILE = ssl.certPath; process.env.SSL_KEY_FILE = ssl.keyPath; } } } const config = await loadConfigAsync(env); const port = await getAvailablePortAsync({ projectRoot, defaultPort: options.port }); webpackServerPort = port; _internal().ProjectUtils.logInfo(projectRoot, WEBPACK_LOG_TAG, `Starting Webpack on port ${webpackServerPort} in ${_chalk().default.underline(env.mode)} mode.`); const protocol = env.https ? 'https' : 'http'; if (isTargetingNative()) { await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { expoServerPort: webpackServerPort, packagerPort: webpackServerPort }); } // Create a webpack compiler that is configured with custom messages. const compiler = (0, _webpack().default)(config); // Create the middleware required for interacting with a native runtime (Expo Go, or a development build). let nativeMiddleware = null; if ((_config$devServer = config.devServer) !== null && _config$devServer !== void 0 && _config$devServer.before) { nativeMiddleware = createNativeDevServerMiddleware(projectRoot, { port, compiler, forceManifestType: options.forceManifestType }); // Inject the native manifest middleware. const originalBefore = config.devServer.before.bind(config.devServer.before); config.devServer.before = (app, server, compiler) => { var _nativeMiddleware; originalBefore(app, server, compiler); if ((_nativeMiddleware = nativeMiddleware) !== null && _nativeMiddleware !== void 0 && _nativeMiddleware.middleware) { app.use(nativeMiddleware.middleware); } }; } const server = new (_webpackDevServer().default)(compiler, config.devServer); // Launch WebpackDevServer. server.listen(port, _internal().WebpackEnvironment.HOST, function (error) { if (nativeMiddleware) { attachNativeDevServerMiddlewareToDevServer(projectRoot, { server: this, ...nativeMiddleware }); } if (error) { _internal().ProjectUtils.logError(projectRoot, WEBPACK_LOG_TAG, error.message); } if (typeof options.onWebpackFinished === 'function') { options.onWebpackFinished(error); } }); webpackDevServerInstance = server; await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { webpackServerPort }); const host = _internal().ip.address(); const url = `${protocol}://${host}:${port}`; // Extend the close method to ensure that we clean up the local info. const originalClose = server.close.bind(server); server.close = callback => { return originalClose(err => { _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { webpackServerPort: null }).finally(() => { callback === null || callback === void 0 ? void 0 : callback(err); webpackDevServerInstance = null; webpackServerPort = null; }); }); }; return { server, location: { url, port, protocol, host }, // Match the native protocol. messageSocket: { broadcast: broadcastMessage } }; } async function stopAsync(projectRoot) { if (webpackDevServerInstance) { await new Promise(res => { if (webpackDevServerInstance) { _internal().ProjectUtils.logInfo(projectRoot, WEBPACK_LOG_TAG, '\u203A Stopping Webpack server'); webpackDevServerInstance.close(res); } }); } } async function openAsync(projectRoot, options) { if (!webpackDevServerInstance) { await startAsync(projectRoot, options); } await openProjectAsync(projectRoot); } async function compileWebAppAsync(projectRoot, compiler) { // We generate the stats.json file in the webpack-config const { warnings } = await new Promise((resolve, reject) => compiler.run((error, stats) => { let messages; if (error) { if (!error.message) { return reject(error); } messages = (0, _formatWebpackMessages().default)({ errors: [error.message], warnings: [], _showErrors: true, _showWarnings: true }); } else { messages = (0, _formatWebpackMessages().default)(stats.toJson({ all: false, warnings: true, errors: true })); } if (messages.errors.length) { // Only keep the first error. Others are often indicative // of the same problem, but confuse the reader with noise. if (messages.errors.length > 1) { messages.errors.length = 1; } return reject(new (_internal().XDLError)('WEBPACK_BUNDLE', messages.errors.join('\n\n'))); } if (_getenv().default.boolish('EXPO_WEB_BUILD_STRICT', false) && _getenv().default.boolish('CI', false) && messages.warnings.length) { _internal().ProjectUtils.logWarning(projectRoot, WEBPACK_LOG_TAG, _chalk().default.yellow('\nTreating warnings as errors because `process.env.CI = true` and `process.env.EXPO_WEB_BUILD_STRICT = true`. \n' + 'Most CI servers set it automatically.\n')); return reject(new (_internal().XDLError)('WEBPACK_BUNDLE', messages.warnings.join('\n\n'))); } resolve({ warnings: messages.warnings }); })); return { warnings }; } async function bundleWebAppAsync(projectRoot, config) { const compiler = (0, _webpack().default)(config); try { const { warnings } = await compileWebAppAsync(projectRoot, compiler); if (warnings.length) { _internal().ProjectUtils.logWarning(projectRoot, WEBPACK_LOG_TAG, _chalk().default.yellow('Compiled with warnings.\n')); _internal().ProjectUtils.logWarning(projectRoot, WEBPACK_LOG_TAG, warnings.join('\n\n')); } else { _internal().ProjectUtils.logInfo(projectRoot, WEBPACK_LOG_TAG, _chalk().default.green('Compiled successfully.\n')); } } catch (error) { _internal().ProjectUtils.logError(projectRoot, WEBPACK_LOG_TAG, _chalk().default.red('Failed to compile.\n')); throw error; } } async function bundleAsync(projectRoot, options) { const fullOptions = transformCLIOptions({ ...options }); const env = await getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, { ...fullOptions, // Force production mode: 'production' }); // @ts-ignore if (typeof env.offline !== 'undefined') { throw new Error('offline support must be added manually: https://expo.fyi/enabling-web-service-workers'); } if (fullOptions.clear) { await clearWebCacheAsync(projectRoot, env.mode); } const config = await loadConfigAsync(env); await bundleWebAppAsync(projectRoot, config); } /** * Get the URL for the running instance of Webpack dev server. * * @param projectRoot */ async function getUrlAsync(projectRoot) { if (!webpackDevServerInstance) { return null; } const host = _internal().ip.address(); const protocol = await getProtocolAsync(projectRoot); return `${protocol}://${host}:${webpackServerPort}`; } async function getProtocolAsync(projectRoot) { // TODO: Bacon: Handle when not in expo const { https } = await _internal().ProjectSettings.readAsync(projectRoot); return https === true ? 'https' : 'http'; } async function getAvailablePortAsync(options) { try { const defaultPort = 'defaultPort' in options && options.defaultPort ? options.defaultPort : _internal().WebpackEnvironment.DEFAULT_PORT; const port = await (0, _internal().choosePortAsync)(options.projectRoot, { defaultPort, host: 'host' in options && options.host ? options.host : _internal().WebpackEnvironment.HOST }); if (!port) { throw new Error(`Port ${defaultPort} not available.`); } return port; } catch (error) { throw new (_internal().XDLError)('NO_PORT_FOUND', error.message); } } function setMode(mode) { process.env.BABEL_ENV = mode; process.env.NODE_ENV = mode; } function validateBoolOption(name, value, defaultValue) { if (typeof value === 'undefined') { value = defaultValue; } if (typeof value !== 'boolean') { throw new (_internal().XDLError)('WEBPACK_INVALID_OPTION', `'${name}' option must be a boolean.`); } return value; } function transformCLIOptions(options) { // Transform the CLI flags into more explicit values return { ...options, isImageEditingEnabled: options.pwa }; } async function applyOptionsToProjectSettingsAsync(projectRoot, options) { const newSettings = {}; // Change settings before reading them if (typeof options.https === 'boolean') { newSettings.https = options.https; } if (Object.keys(newSettings).length) { await _internal().ProjectSettings.setAsync(projectRoot, newSettings); } return await _internal().ProjectSettings.readAsync(projectRoot); } async function getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, options) { const { dev, https } = await applyOptionsToProjectSettingsAsync(projectRoot, options); const mode = typeof options.mode === 'string' ? options.mode : dev ? 'development' : 'production'; const isImageEditingEnabled = validateBoolOption('isImageEditingEnabled', options.isImageEditingEnabled, true); return { projectRoot, pwa: isImageEditingEnabled, logger: _internal().ProjectUtils.getLogger(projectRoot), isImageEditingEnabled, mode, https, ...(options.webpackEnv || {}) }; } async function getSSLCertAsync({ name, directory }) { console.log(_chalk().default.magenta`Ensuring auto SSL certificate is created (you might need to re-run with sudo)`); try { const result = await devcert().certificateFor(name); if (result) { const { key, cert } = result; const folder = path().join(directory, '.expo', 'web', 'development', 'ssl'); await _fsExtra().default.ensureDir(folder); const keyPath = path().join(folder, `key-${name}.pem`); await _fsExtra().default.writeFile(keyPath, key); const certPath = path().join(folder, `cert-${name}.pem`); await _fsExtra().default.writeFile(certPath, cert); return { keyPath, certPath }; } return result; } catch (error) { console.log(`Error creating SSL certificates: ${error}`); } return false; } function applyEnvironmentVariables(config) { // Use EXPO_DEBUG_WEB=true to enable debugging features for cases where the prod build // has errors that aren't caught in development mode. // Related: https://github.com/expo/expo-cli/issues/614 if (_internal().WebpackEnvironment.isDebugModeEnabled() && config.mode === 'production') { console.log(_chalk().default.bgYellow.black('Bundling the project in debug mode.')); const output = config.output || {}; const optimization = config.optimization || {}; // Enable line to line mapped mode for all/specified modules. // Line to line mapped mode uses a simple SourceMap where each line of the generated source is mapped to the same line of the original source. // It’s a performance optimization. Only use it if your performance need to be better and you are sure that input lines match which generated lines. // true enables it for all modules (not recommended) output.devtoolLineToLine = true; // Add comments that describe the file import/exports. // This will make it easier to debug. output.pathinfo = true; // Instead of numeric ids, give modules readable names for better debugging. optimization.namedModules = true; // Instead of numeric ids, give chunks readable names for better debugging. optimization.namedChunks = true; // Readable ids for better debugging. // @ts-ignore Property 'moduleIds' does not exist. optimization.moduleIds = 'named'; // if optimization.namedChunks is enabled optimization.chunkIds is set to 'named'. // This will manually enable it just to be safe. // @ts-ignore Property 'chunkIds' does not exist. optimization.chunkIds = 'named'; if (optimization.splitChunks) { optimization.splitChunks.name = true; } Object.assign(config, { output, optimization }); } return config; } async function loadConfigAsync(env, argv) { setMode(env.mode); // Check if the project has a webpack.config.js in the root. const projectWebpackConfig = path().resolve(env.projectRoot, 'webpack.config.js'); let config; if (_fsExtra().default.existsSync(projectWebpackConfig)) { const webpackConfig = require(projectWebpackConfig); if (typeof webpackConfig === 'function') { config = await webpackConfig(env, argv); } else { config = webpackConfig; } } else { // Fallback to the default expo webpack config. const loadDefaultConfigAsync = require('@expo/webpack-config'); config = await loadDefaultConfigAsync(env, argv); } return applyEnvironmentVariables(config); } async function openProjectAsync(projectRoot) { try { const url = await _internal().UrlUtils.constructWebAppUrlAsync(projectRoot, { hostType: 'localhost' }); if (!url) { throw new Error('Webpack Dev Server is not running'); } (0, _betterOpn().default)(url); return { success: true, url }; } catch (e) { _internal().Logger.global.error(`Couldn't start project on web: ${e.message}`); return { success: false, error: e }; } } //# sourceMappingURL=Webpack.js.map