|
1 | 1 | import path from 'path' |
2 | 2 | import * as fs from 'fs' |
3 | 3 | import { tmpdir } from 'os' |
| 4 | +import * as childProcess from 'child_process' |
4 | 5 |
|
5 | 6 | import { info, rmRf, execCommand, buildBundleConfig, mkdir } from './utils' |
6 | 7 | import type { BundleArgs, BundlerConfig, Hashes } from './types' |
7 | 8 | import { checkout, gitRestore } from './git' |
8 | 9 | import { hashes, removeUnchangedAssets } from './diff' |
9 | 10 | import { metroBundle } from './metroBundle' |
10 | 11 |
|
11 | | -const { runHermesEmitBinaryCommand } = require('appcenter-cli/dist/commands/codepush/lib/react-native-utils') |
12 | | - |
13 | 12 | export async function bundle(args: BundleArgs) { |
14 | 13 | const { base } = args |
15 | 14 | const bundlerConfig = buildBundleConfig(args) |
@@ -104,3 +103,154 @@ const readBaseHashes = async (bundlerConfig: BundlerConfig, base: string): Promi |
104 | 103 |
|
105 | 104 | return JSON.parse(fs.readFileSync(process.env.BASE_ASSETS_PATH, 'utf8')) |
106 | 105 | } |
| 106 | + |
| 107 | +export async function runHermesEmitBinaryCommand( |
| 108 | + bundleName: string, |
| 109 | + outputFolder: string, |
| 110 | + sourcemapOutput: string, |
| 111 | + extraHermesFlags: string[] |
| 112 | +): Promise<void> { |
| 113 | + const hermesArgs: string[] = [] |
| 114 | + |
| 115 | + Array.prototype.push.apply(hermesArgs, [ |
| 116 | + '-emit-binary', |
| 117 | + '-out', |
| 118 | + path.join(outputFolder, bundleName + '.hbc'), |
| 119 | + path.join(outputFolder, bundleName), |
| 120 | + ...extraHermesFlags, |
| 121 | + ]) |
| 122 | + |
| 123 | + if (sourcemapOutput) { |
| 124 | + hermesArgs.push('-output-source-map') |
| 125 | + } |
| 126 | + |
| 127 | + hermesArgs.push('-w') |
| 128 | + |
| 129 | + await buildHermes(bundleName, outputFolder, hermesArgs) |
| 130 | + |
| 131 | + if (sourcemapOutput) { |
| 132 | + await composeSourceMaps(bundleName, outputFolder, sourcemapOutput) |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +async function buildHermes(bundleName: string, outputFolder: string, hermesArgs: string[]) { |
| 137 | + const hermesCommand = getHermesCommand() |
| 138 | + const hermesProcess = childProcess.spawn(hermesCommand, hermesArgs) |
| 139 | + return new Promise<void>((resolve, reject) => { |
| 140 | + hermesProcess.stdout.on('data', (data: Buffer) => { |
| 141 | + console.log(data.toString().trim()) |
| 142 | + }) |
| 143 | + |
| 144 | + hermesProcess.stderr.on('data', (data: Buffer) => { |
| 145 | + console.error(data.toString().trim()) |
| 146 | + }) |
| 147 | + |
| 148 | + hermesProcess.on('close', (exitCode: number, signal: string) => { |
| 149 | + if (exitCode !== 0) { |
| 150 | + reject(new Error(`"hermes" command failed (exitCode=${exitCode}, signal=${signal}).`)) |
| 151 | + } |
| 152 | + // Copy HBC bundle to overwrite JS bundle |
| 153 | + const source = path.join(outputFolder, bundleName + '.hbc') |
| 154 | + const destination = path.join(outputFolder, bundleName) |
| 155 | + fs.copyFile(source, destination, (err) => { |
| 156 | + if (err) { |
| 157 | + console.error(err) |
| 158 | + reject( |
| 159 | + new Error( |
| 160 | + `Copying file ${source} to ${destination} failed. "hermes" previously exited with code ${exitCode}.` |
| 161 | + ) |
| 162 | + ) |
| 163 | + } |
| 164 | + fs.unlink(source, (error) => { |
| 165 | + if (error) { |
| 166 | + console.error(error) |
| 167 | + reject(error) |
| 168 | + } |
| 169 | + resolve() |
| 170 | + }) |
| 171 | + }) |
| 172 | + }) |
| 173 | + }) |
| 174 | +} |
| 175 | + |
| 176 | +async function composeSourceMaps(bundleName: string, outputFolder: string, sourcemapOutput: string) { |
| 177 | + const composeSourceMapsPath = getComposeSourceMapsPath() |
| 178 | + if (!composeSourceMapsPath) { |
| 179 | + throw new Error('react-native compose-source-maps.js scripts is not found') |
| 180 | + } |
| 181 | + const jsCompilerSourceMapFile = path.join(outputFolder, bundleName + '.hbc' + '.map') |
| 182 | + if (!fs.existsSync(jsCompilerSourceMapFile)) { |
| 183 | + throw new Error(`sourcemap file ${jsCompilerSourceMapFile} is not found`) |
| 184 | + } |
| 185 | + |
| 186 | + return new Promise((resolve, reject) => { |
| 187 | + const composeSourceMapsArgs = [ |
| 188 | + composeSourceMapsPath, |
| 189 | + sourcemapOutput, |
| 190 | + jsCompilerSourceMapFile, |
| 191 | + '-o', |
| 192 | + sourcemapOutput, |
| 193 | + ] |
| 194 | + |
| 195 | + // https://github.com/facebook/react-native/blob/master/react.gradle#L211 |
| 196 | + // https://github.com/facebook/react-native/blob/master/scripts/react-native-xcode.sh#L178 |
| 197 | + // packager.sourcemap.map + hbc.sourcemap.map = sourcemap.map |
| 198 | + const composeSourceMapsProcess = childProcess.spawn('node', composeSourceMapsArgs) |
| 199 | + console.log(`${composeSourceMapsPath} ${composeSourceMapsArgs.join(' ')}`) |
| 200 | + |
| 201 | + composeSourceMapsProcess.stdout.on('data', (data: Buffer) => { |
| 202 | + console.log(data.toString().trim()) |
| 203 | + }) |
| 204 | + |
| 205 | + composeSourceMapsProcess.stderr.on('data', (data: Buffer) => { |
| 206 | + console.error(data.toString().trim()) |
| 207 | + }) |
| 208 | + |
| 209 | + composeSourceMapsProcess.on('close', (exitCode: number, signal: string) => { |
| 210 | + if (exitCode !== 0) { |
| 211 | + reject(new Error(`"compose-source-maps" command failed (exitCode=${exitCode}, signal=${signal}).`)) |
| 212 | + } |
| 213 | + |
| 214 | + // Delete the HBC sourceMap, otherwise it will be included in 'code-push' bundle as well |
| 215 | + fs.unlink(jsCompilerSourceMapFile, (err) => { |
| 216 | + if (err) { |
| 217 | + console.error(err) |
| 218 | + reject(err) |
| 219 | + } |
| 220 | + |
| 221 | + resolve(null) |
| 222 | + }) |
| 223 | + }) |
| 224 | + }) |
| 225 | +} |
| 226 | + |
| 227 | +function getHermesCommand() { |
| 228 | + return `${getReactNativePackagePath()}/sdks/hermesc/${getHermesOSBin()}/hermesc` |
| 229 | +} |
| 230 | + |
| 231 | +function getReactNativePackagePath() { |
| 232 | + return path.join('node_modules', 'react-native') |
| 233 | +} |
| 234 | + |
| 235 | +function getHermesOSBin() { |
| 236 | + switch (process.platform) { |
| 237 | + case 'win32': |
| 238 | + return 'win64-bin' |
| 239 | + case 'darwin': |
| 240 | + return 'osx-bin' |
| 241 | + case 'freebsd': |
| 242 | + case 'linux': |
| 243 | + case 'sunos': |
| 244 | + default: |
| 245 | + return 'linux64-bin' |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +function getComposeSourceMapsPath() { |
| 250 | + // detect if compose-source-maps.js script exists |
| 251 | + const composeSourceMapsPath = path.join(getReactNativePackagePath(), 'scripts', 'compose-source-maps.js') |
| 252 | + if (fs.existsSync(composeSourceMapsPath)) { |
| 253 | + return composeSourceMapsPath |
| 254 | + } |
| 255 | + return null |
| 256 | +} |
0 commit comments