@expo/xdl
Version:
The Expo Development Library
765 lines (604 loc) • 21.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.restartAsync = restartAsync;
exports.printConnectionInstructions = printConnectionInstructions;
exports.startAsync = startAsync;
exports.stopAsync = stopAsync;
exports.openAsync = openAsync;
exports.compileWebAppAsync = compileWebAppAsync;
exports.bundleWebAppAsync = bundleWebAppAsync;
exports.bundleAsync = bundleAsync;
exports.getProjectNameAsync = getProjectNameAsync;
exports.isRunning = isRunning;
exports.getServer = getServer;
exports.getPort = getPort;
exports.getUrlAsync = getUrlAsync;
exports.invokeWebpackConfigAsync = invokeWebpackConfigAsync;
exports.openProjectAsync = openProjectAsync;
function _config() {
const data = require("@expo/config");
_config = function () {
return data;
};
return data;
}
function devcert() {
const data = _interopRequireWildcard(require("@expo/devcert"));
devcert = function () {
return data;
};
return data;
}
function _packageManager() {
const data = require("@expo/package-manager");
_packageManager = 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 _WebpackDevServerUtils() {
const data = require("react-dev-utils/WebpackDevServerUtils");
_WebpackDevServerUtils = function () {
return data;
};
return data;
}
function _formatWebpackMessages() {
const data = _interopRequireDefault(require("react-dev-utils/formatWebpackMessages"));
_formatWebpackMessages = function () {
return data;
};
return data;
}
function _openBrowser() {
const data = _interopRequireDefault(require("react-dev-utils/openBrowser"));
_openBrowser = 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 _Logger() {
const data = _interopRequireDefault(require("./Logger"));
_Logger = function () {
return data;
};
return data;
}
function ProjectSettings() {
const data = _interopRequireWildcard(require("./ProjectSettings"));
ProjectSettings = function () {
return data;
};
return data;
}
function UrlUtils() {
const data = _interopRequireWildcard(require("./UrlUtils"));
UrlUtils = function () {
return data;
};
return data;
}
function Versions() {
const data = _interopRequireWildcard(require("./Versions"));
Versions = function () {
return data;
};
return data;
}
function _XDLError() {
const data = _interopRequireDefault(require("./XDLError"));
_XDLError = function () {
return data;
};
return data;
}
function _ip() {
const data = _interopRequireDefault(require("./ip"));
_ip = function () {
return data;
};
return data;
}
function _TerminalLink() {
const data = require("./logs/TerminalLink");
_TerminalLink = function () {
return data;
};
return data;
}
function ProjectUtils() {
const data = _interopRequireWildcard(require("./project/ProjectUtils"));
ProjectUtils = function () {
return data;
};
return data;
}
function _WebpackEnvironment() {
const data = require("./webpack-utils/WebpackEnvironment");
_WebpackEnvironment = function () {
return data;
};
return data;
}
function _createWebpackCompiler() {
const data = _interopRequireWildcard(require("./webpack-utils/createWebpackCompiler"));
_createWebpackCompiler = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (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;
async function restartAsync(projectRoot, options = {}) {
await stopAsync(projectRoot);
return await startAsync(projectRoot, options);
}
let devServerInfo = null;
function printConnectionInstructions(projectRoot, options = {}) {
if (!devServerInfo) return;
(0, _createWebpackCompiler().printInstructions)(projectRoot, {
appName: devServerInfo.appName,
urls: devServerInfo.urls,
showInDevtools: false,
...options
});
}
async function clearWebCacheAsync(projectRoot, mode) {
const cacheFolder = path().join(projectRoot, '.expo', 'web', 'cache', mode);
ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, _chalk().default.dim(`Clearing ${mode} cache directory...`));
try {
await _fsExtra().default.remove(cacheFolder);
} catch (_unused) {}
}
async function startAsync(projectRoot, options = {}, deprecatedVerbose) {
if (typeof deprecatedVerbose !== 'undefined') {
throw new (_XDLError().default)('WEBPACK_DEPRECATED', 'startAsync(root, options, verbose): The `verbose` option is deprecated.');
}
const serverName = 'Webpack';
if (webpackDevServerInstance) {
ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, _chalk().default.red(`${serverName} 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 createWebpackConfigAsync(env, fullOptions);
const port = await getAvailablePortAsync({
defaultPort: options.port
});
webpackServerPort = port;
ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, `Starting ${serverName} on port ${webpackServerPort} in ${_chalk().default.underline(env.mode)} mode.`);
const protocol = env.https ? 'https' : 'http';
const urls = (0, _WebpackDevServerUtils().prepareUrls)(protocol, '::', webpackServerPort);
const useYarn = (0, _packageManager().isUsingYarn)(projectRoot);
const appName = await getProjectNameAsync(projectRoot);
const nonInteractive = validateBoolOption('nonInteractive', options.nonInteractive, !process.stdout.isTTY);
devServerInfo = {
urls,
protocol,
useYarn,
appName,
nonInteractive,
port: webpackServerPort
};
const server = await new Promise(resolve => {
// Create a webpack compiler that is configured with custom messages.
const compiler = (0, _createWebpackCompiler().default)({
projectRoot,
appName,
config,
urls,
nonInteractive,
webpackFactory: _webpack().default,
onFinished: () => resolve(server)
});
const server = new (_webpackDevServer().default)(compiler, config.devServer); // Launch WebpackDevServer.
server.listen(port, _WebpackEnvironment().HOST, error => {
if (error) {
ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, error.message);
}
if (typeof options.onWebpackFinished === 'function') {
options.onWebpackFinished(error);
}
});
webpackDevServerInstance = server;
});
await ProjectSettings().setPackagerInfoAsync(projectRoot, {
webpackServerPort
});
const host = _ip().default.address();
const url = `${protocol}://${host}:${port}`;
return {
url,
server,
port,
protocol,
host
};
}
async function stopAsync(projectRoot) {
if (webpackDevServerInstance) {
await new Promise(res => {
if (webpackDevServerInstance) {
ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, '\u203A Stopping Webpack server');
webpackDevServerInstance.close(res);
}
});
webpackDevServerInstance = null;
devServerInfo = null;
webpackServerPort = null;
await ProjectSettings().setPackagerInfoAsync(projectRoot, {
webpackServerPort: null
});
}
}
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 Error(messages.errors.join('\n\n')));
}
if (_getenv().default.boolish('EXPO_WEB_BUILD_STRICT', false) && _getenv().default.boolish('CI', false) && messages.warnings.length) {
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 Error(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) {
ProjectUtils().logWarning(projectRoot, WEBPACK_LOG_TAG, _chalk().default.yellow('Compiled with warnings.\n'));
ProjectUtils().logWarning(projectRoot, WEBPACK_LOG_TAG, warnings.join('\n\n'));
} else {
ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, _chalk().default.green('Compiled successfully.\n'));
}
} catch (error) {
ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, _chalk().default.red('Failed to compile.\n'));
throw error;
}
}
async function bundleAsync(projectRoot, options) {
var _config$plugins;
const fullOptions = transformCLIOptions({ ...options
});
const env = await getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, { ...fullOptions,
// Force production
mode: 'production'
});
if (typeof env.offline === 'undefined') {
try {
const expoConfig = (0, _config().getConfig)(projectRoot, {
skipSDKVersionRequirement: true
}); // If offline isn't defined, check the version and keep offline enabled for SDK 38 and prior
if (expoConfig.exp.sdkVersion) if (Versions().lteSdkVersion(expoConfig.exp, '38.0.0')) {
env.offline = true;
}
} catch (_unused2) {// Ignore the error thrown by projects without an Expo config.
}
}
if (fullOptions.clear) {
await clearWebCacheAsync(projectRoot, env.mode);
}
const config = await createWebpackConfigAsync(env, fullOptions);
await bundleWebAppAsync(projectRoot, config);
const hasSWPlugin = (_config$plugins = config.plugins) === null || _config$plugins === void 0 ? void 0 : _config$plugins.find(item => {
var _item$constructor;
return (item === null || item === void 0 ? void 0 : (_item$constructor = item.constructor) === null || _item$constructor === void 0 ? void 0 : _item$constructor.name) === 'GenerateSW';
});
if (!hasSWPlugin) {
ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, _chalk().default.green(`Offline (PWA) support is not enabled in this build. ${_chalk().default.dim((0, _TerminalLink().learnMore)('https://expo.fyi/enabling-web-service-workers'))}\n`));
}
}
async function getProjectNameAsync(projectRoot) {
var _getNameFromConfig$we;
const {
exp
} = (0, _config().getConfig)(projectRoot, {
skipSDKVersionRequirement: true
});
const webName = (_getNameFromConfig$we = (0, _config().getNameFromConfig)(exp).webName) !== null && _getNameFromConfig$we !== void 0 ? _getNameFromConfig$we : exp.name;
return webName;
}
function isRunning() {
return !!webpackDevServerInstance;
}
function getServer(projectRoot) {
if (webpackDevServerInstance == null) {
ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, 'Webpack is not running.');
}
return webpackDevServerInstance;
}
function getPort() {
return webpackServerPort;
}
/**
* Get the URL for the running instance of Webpack dev server.
*
* @param projectRoot
*/
async function getUrlAsync(projectRoot) {
const devServer = getServer(projectRoot);
if (!devServer) {
return null;
}
const host = _ip().default.address();
const protocol = await getProtocolAsync(projectRoot);
return `${protocol}://${host}:${webpackServerPort}`;
}
async function getProtocolAsync(projectRoot) {
// TODO: Bacon: Handle when not in expo
const {
https
} = await ProjectSettings().readAsync(projectRoot);
return https === true ? 'https' : 'http';
}
async function getAvailablePortAsync(options = {}) {
try {
const defaultPort = 'defaultPort' in options && options.defaultPort ? options.defaultPort : _WebpackEnvironment().DEFAULT_PORT;
const port = await (0, _WebpackDevServerUtils().choosePort)('host' in options && options.host ? options.host : _WebpackEnvironment().HOST, defaultPort);
if (!port) throw new Error(`Port ${defaultPort} not available.`);else return port;
} catch (error) {
throw new (_XDLError().default)('NO_PORT_FOUND', 'No available 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 (_XDLError().default)('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 createWebpackConfigAsync(env, options = {}) {
setMode(env.mode);
let config;
if (options.unimodulesOnly) {
const {
withUnimodules
} = require('@expo/webpack-config/addons');
config = withUnimodules({}, env);
} else {
config = await invokeWebpackConfigAsync(env);
}
return config;
}
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 ProjectSettings().setAsync(projectRoot, newSettings);
}
return await 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,
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 ((0, _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 invokeWebpackConfigAsync(env, argv) {
// 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 createExpoWebpackConfigAsync = require('@expo/webpack-config');
config = await createExpoWebpackConfigAsync(env, argv);
}
return applyEnvironmentVariables(config);
}
async function openProjectAsync(projectRoot) {
try {
const url = await UrlUtils().constructWebAppUrlAsync(projectRoot, {
hostType: 'localhost'
});
if (!url) {
throw new Error('Webpack Dev Server is not running');
}
(0, _openBrowser().default)(url);
return {
success: true,
url
};
} catch (e) {
_Logger().default.global.error(`Couldn't start project on web: ${e.message}`);
return {
success: false,
error: e
};
}
}
//# sourceMappingURL=__sourcemaps__/Webpack.js.map