You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

598 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"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.
// Its 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