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.

252 lines
12 KiB

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildHermesBundleAsync = buildHermesBundleAsync;
exports.createHermesSourcemapAsync = createHermesSourcemapAsync;
exports.getHermesBytecodeBundleVersionAsync = getHermesBytecodeBundleVersionAsync;
exports.isEnableHermesManaged = isEnableHermesManaged;
exports.isHermesBytecodeBundleAsync = isHermesBytecodeBundleAsync;
exports.maybeInconsistentEngineAndroidAsync = maybeInconsistentEngineAndroidAsync;
exports.maybeInconsistentEngineIosAsync = maybeInconsistentEngineIosAsync;
exports.maybeThrowFromInconsistentEngineAsync = maybeThrowFromInconsistentEngineAsync;
exports.parseGradleProperties = parseGradleProperties;
function _spawnAsync() {
const data = _interopRequireDefault(require("@expo/spawn-async"));
_spawnAsync = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _os() {
const data = _interopRequireDefault(require("os"));
_os = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _process() {
const data = _interopRequireDefault(require("process"));
_process = function () {
return data;
};
return data;
}
function _semver() {
const data = _interopRequireDefault(require("semver"));
_semver = function () {
return data;
};
return data;
}
function _importMetroFromProject() {
const data = require("./metro/importMetroFromProject");
_importMetroFromProject = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function isEnableHermesManaged(expoConfig, platform) {
switch (platform) {
case 'android':
{
var _expoConfig$android$j2, _expoConfig$android2;
if (!gteSdkVersion(expoConfig, '42.0.0')) {
// Hermes on Android is supported after SDK 42.
return false;
}
if (gteSdkVersion(expoConfig, '48.0.0')) {
var _expoConfig$android$j, _expoConfig$android;
// Hermes on Android is enabled by default after SDK 48.
return ((_expoConfig$android$j = (_expoConfig$android = expoConfig.android) === null || _expoConfig$android === void 0 ? void 0 : _expoConfig$android.jsEngine) !== null && _expoConfig$android$j !== void 0 ? _expoConfig$android$j : expoConfig.jsEngine) !== 'jsc';
}
return ((_expoConfig$android$j2 = (_expoConfig$android2 = expoConfig.android) === null || _expoConfig$android2 === void 0 ? void 0 : _expoConfig$android2.jsEngine) !== null && _expoConfig$android$j2 !== void 0 ? _expoConfig$android$j2 : expoConfig.jsEngine) === 'hermes';
}
case 'ios':
{
var _expoConfig$ios$jsEng2, _expoConfig$ios2;
if (!gteSdkVersion(expoConfig, '43.0.0')) {
// Hermes on iOS is supported after SDK 43.
return false;
}
if (gteSdkVersion(expoConfig, '48.0.0')) {
var _expoConfig$ios$jsEng, _expoConfig$ios;
// Hermes on iOS is enabled by default after SDK 48.
return ((_expoConfig$ios$jsEng = (_expoConfig$ios = expoConfig.ios) === null || _expoConfig$ios === void 0 ? void 0 : _expoConfig$ios.jsEngine) !== null && _expoConfig$ios$jsEng !== void 0 ? _expoConfig$ios$jsEng : expoConfig.jsEngine) !== 'jsc';
}
return ((_expoConfig$ios$jsEng2 = (_expoConfig$ios2 = expoConfig.ios) === null || _expoConfig$ios2 === void 0 ? void 0 : _expoConfig$ios2.jsEngine) !== null && _expoConfig$ios$jsEng2 !== void 0 ? _expoConfig$ios$jsEng2 : expoConfig.jsEngine) === 'hermes';
}
default:
return false;
}
}
async function buildHermesBundleAsync(projectRoot, code, map, optimize = false) {
const tempDir = _path().default.join(_os().default.tmpdir(), `expo-bundler-${_process().default.pid}`);
await _fsExtra().default.ensureDir(tempDir);
try {
const tempBundleFile = _path().default.join(tempDir, 'index.bundle');
const tempSourcemapFile = _path().default.join(tempDir, 'index.bundle.map');
await _fsExtra().default.writeFile(tempBundleFile, code);
await _fsExtra().default.writeFile(tempSourcemapFile, map);
const tempHbcFile = _path().default.join(tempDir, 'index.hbc');
const hermesCommand = (0, _importMetroFromProject().importHermesCommandFromProject)(projectRoot);
const args = ['-emit-binary', '-out', tempHbcFile, tempBundleFile, '-output-source-map'];
if (optimize) {
args.push('-O');
}
await (0, _spawnAsync().default)(hermesCommand, args);
const [hbc, sourcemap] = await Promise.all([_fsExtra().default.readFile(tempHbcFile), createHermesSourcemapAsync(projectRoot, map, `${tempHbcFile}.map`)]);
return {
hbc,
sourcemap
};
} finally {
await _fsExtra().default.remove(tempDir);
}
}
async function createHermesSourcemapAsync(projectRoot, sourcemap, hermesMapFile) {
const composeSourceMaps = (0, _importMetroFromProject().importMetroSourceMapComposeSourceMapsFromProject)(projectRoot);
const bundlerSourcemap = JSON.parse(sourcemap);
const hermesSourcemap = await _fsExtra().default.readJSON(hermesMapFile);
return JSON.stringify(composeSourceMaps([bundlerSourcemap, hermesSourcemap]));
}
function parseGradleProperties(content) {
const result = {};
for (let line of content.split('\n')) {
line = line.trim();
if (!line || line.startsWith('#')) {
continue;
}
const sepIndex = line.indexOf('=');
const key = line.substr(0, sepIndex);
const value = line.substr(sepIndex + 1);
result[key] = value;
}
return result;
}
async function maybeThrowFromInconsistentEngineAsync(projectRoot, configFilePath, platform, isHermesManaged) {
const configFileName = _path().default.basename(configFilePath);
if (platform === 'android' && (await maybeInconsistentEngineAndroidAsync(projectRoot, isHermesManaged))) {
throw new Error(`JavaScript engine configuration is inconsistent between ${configFileName} and Android native project.\n` + `In ${configFileName}: Hermes is ${isHermesManaged ? 'enabled' : 'not enabled'}\n` + `In Android native project: Hermes is ${isHermesManaged ? 'not enabled' : 'enabled'}\n` + `Please check the following files for inconsistencies:\n` + ` - ${configFilePath}\n` + ` - ${_path().default.join(projectRoot, 'android', 'gradle.properties')}\n` + ` - ${_path().default.join(projectRoot, 'android', 'app', 'build.gradle')}\n` + 'Learn more: https://expo.fyi/hermes-android-config');
}
if (platform === 'ios' && (await maybeInconsistentEngineIosAsync(projectRoot, isHermesManaged))) {
throw new Error(`JavaScript engine configuration is inconsistent between ${configFileName} and iOS native project.\n` + `In ${configFileName}: Hermes is ${isHermesManaged ? 'enabled' : 'not enabled'}\n` + `In iOS native project: Hermes is ${isHermesManaged ? 'not enabled' : 'enabled'}\n` + `Please check the following files for inconsistencies:\n` + ` - ${configFilePath}\n` + ` - ${_path().default.join(projectRoot, 'ios', 'Podfile')}\n` + ` - ${_path().default.join(projectRoot, 'ios', 'Podfile.properties.json')}\n` + 'Learn more: https://expo.fyi/hermes-ios-config');
}
}
async function maybeInconsistentEngineAndroidAsync(projectRoot, isHermesManaged) {
// Trying best to check android native project if by chance to be consistent between app config
// Check android/app/build.gradle for "enableHermes: true"
const appBuildGradlePath = _path().default.join(projectRoot, 'android', 'app', 'build.gradle');
if (_fsExtra().default.existsSync(appBuildGradlePath)) {
const content = await _fsExtra().default.readFile(appBuildGradlePath, 'utf8');
const isPropsReference = content.search(/^\s*(enableHermes:|hermesEnabled\s*=)\s*\(findProperty\('expo.jsEngine'\) \?: "(jsc|hermes)"\) == "hermes",?\s+/m) >= 0;
const isHermesBare = content.search(/^\s*(enableHermes:|hermesEnabled\s*=)\s*true,?\s+/m) >= 0;
if (!isPropsReference && isHermesManaged !== isHermesBare) {
return true;
}
}
// Check gradle.properties from prebuild template
const gradlePropertiesPath = _path().default.join(projectRoot, 'android', 'gradle.properties');
if (_fsExtra().default.existsSync(gradlePropertiesPath)) {
const props = parseGradleProperties(await _fsExtra().default.readFile(gradlePropertiesPath, 'utf8'));
const isHermesBare = props['expo.jsEngine'] === 'hermes';
if (isHermesManaged !== isHermesBare) {
return true;
}
}
return false;
}
async function maybeInconsistentEngineIosAsync(projectRoot, isHermesManaged) {
// Trying best to check ios native project if by chance to be consistent between app config
// Check ios/Podfile for ":hermes_enabled => true"
const podfilePath = _path().default.join(projectRoot, 'ios', 'Podfile');
if (_fsExtra().default.existsSync(podfilePath)) {
const content = await _fsExtra().default.readFile(podfilePath, 'utf8');
const hermesPropReferences = [
// sdk 45
/^\s*:hermes_enabled\s*=>\s*flags\[:hermes_enabled\]\s*\|\|\s*podfile_properties\['expo.jsEngine'\]\s*==\s*'hermes',?/m,
// <= sdk 44
/^\s*:hermes_enabled\s*=>\s*podfile_properties\['expo.jsEngine'\] == 'hermes',?\s+/m,
// sdk 48
/^\s*:hermes_enabled\s*=>\s*podfile_properties\['expo.jsEngine'\]\s*==\s*nil\s*\|\|\s*podfile_properties\['expo.jsEngine'\]\s*==\s*'hermes',?/m];
const isPropsReference = hermesPropReferences.reduce((prev, curr) => prev || content.search(curr) >= 0, false);
const isHermesBare = content.search(/^\s*:hermes_enabled\s*=>\s*true,?\s+/m) >= 0;
if (!isPropsReference && isHermesManaged !== isHermesBare) {
return true;
}
}
// Check Podfile.properties.json from prebuild template
const podfilePropertiesPath = _path().default.join(projectRoot, 'ios', 'Podfile.properties.json');
if (_fsExtra().default.existsSync(podfilePropertiesPath)) {
const props = await parsePodfilePropertiesAsync(podfilePropertiesPath);
const isHermesBare = props['expo.jsEngine'] === 'hermes';
if (isHermesManaged !== isHermesBare) {
return true;
}
}
return false;
}
// https://github.com/facebook/hermes/blob/release-v0.5/include/hermes/BCGen/HBC/BytecodeFileFormat.h#L24-L25
const HERMES_MAGIC_HEADER = 'c61fbc03c103191f';
async function isHermesBytecodeBundleAsync(file) {
const header = await readHermesHeaderAsync(file);
return header.slice(0, 8).toString('hex') === HERMES_MAGIC_HEADER;
}
async function getHermesBytecodeBundleVersionAsync(file) {
const header = await readHermesHeaderAsync(file);
if (header.slice(0, 8).toString('hex') !== HERMES_MAGIC_HEADER) {
throw new Error('Invalid hermes bundle file');
}
return header.readUInt32LE(8);
}
async function readHermesHeaderAsync(file) {
const fd = await _fsExtra().default.open(file, 'r');
const buffer = Buffer.alloc(12);
await _fsExtra().default.read(fd, buffer, 0, 12, null);
await _fsExtra().default.close(fd);
return buffer;
}
// Cloned from xdl/src/Versions.ts, we cannot use that because of circular dependency
function gteSdkVersion(expJson, sdkVersion) {
if (!expJson.sdkVersion) {
return false;
}
if (expJson.sdkVersion === 'UNVERSIONED') {
return true;
}
try {
return _semver().default.gte(expJson.sdkVersion, sdkVersion);
} catch {
throw new Error(`${expJson.sdkVersion} is not a valid version. Must be in the form of x.y.z`);
}
}
async function parsePodfilePropertiesAsync(podfilePropertiesPath) {
try {
return JSON.parse(await _fsExtra().default.readFile(podfilePropertiesPath, 'utf8'));
} catch {
return {};
}
}
//# sourceMappingURL=HermesBundler.js.map