如何以编程方式 start/stop Metro Bundler
How to programmatically start/stop Metro Bundler
我正在尝试为 React-Native 项目设置持续集成,并 运行 解决端到端测试的一些问题,特别是围绕 Metro 捆绑器。
看来在这种情况下使用react-native脚本并不靠谱:
- iOS 构建自发地在新终端中生成一个捆绑器,并在构建后 运行 保留它。
- Android 构建依赖于必须事先手动启动的 运行ning 实例。
- 除了发送信号(Ctrl+C 或 kill)之外,不能通过其他方式停止捆绑程序。
- 没有与构建同步以确保捆绑程序在应用程序启动时准备好进行处理。
我想编写一个可以启动 Metro 的自定义脚本,运行 服务器准备就绪后进行测试,最后停止服务器以清理环境。
metro 捆绑程序必须 运行 作为一个单独的进程才能处理请求。这样做的方法是使用 Child Process : Spawn 并保持返回的对象正确清理。
这是一个基本脚本,可以同时启动 Metro 和 Gradle,并等待两者根据其日志输出准备就绪。
'use strict';
const cp = require('child_process');
const fs = require('fs');
const readline = require('readline');
// List of sub processes kept for proper cleanup
const children = {};
async function asyncPoint(ms, callback = () => {}) {
return await new Promise(resolve => setTimeout(() => {
resolve(callback());
}, ms));
}
async function fork(name, cmd, args, {readyRegex, timeout} = {}) {
return new Promise((resolve) => {
const close = () => {
delete children[name];
resolve(false);
};
if(timeout) {
setTimeout(() => close, timeout);
}
const child = cp.spawn(
cmd,
args,
{
silent: false,
stdio: [null, 'pipe', 'pipe'],
},
);
child.on('close', close);
child.on('exit', close);
child.on('error', close);
const output = fs.createWriteStream(`./volatile-build-${name}.log`);
const lineCb = (line) => {
console.log(`[${name}] ${line}`);
output.write(line+'\n');
if (readyRegex && line.match(readyRegex)) {
resolve(true);
}
};
readline.createInterface({
input: child.stdout,
}).on('line', lineCb);
readline.createInterface({
input: child.stderr,
}).on('line', lineCb);
children[name] = child;
});
}
async function sighandle() {
console.log('\nClosing...');
Object.values(children).forEach(child => child.kill('SIGTERM'));
await asyncPoint(1000);
process.exit(0);
}
function setSigHandler() {
process.on('SIGINT', sighandle);
process.on('SIGTERM', sighandle);
}
async function main() {
setSigHandler();
// Metro Bundler
const metroSync = fork(
'metro',
process.argv0,
[ // args
'./node_modules/react-native/local-cli/cli.js',
'start',
],
{ // options
readyRegex: /Loading dependency graph, done./,
timeout: 60000,
}
);
// Build APK
const buildSync = fork(
'gradle',
'./android/gradlew',
[ // args
`--project-dir=${__dirname}/android`,
'assembleDebug',
],
{ // options
readyRegex: /BUILD SUCCESSFUL/,
timeout: 300000,
}
);
if (await metroSync && await buildSync) {
// TODO: Run tests here
}
sighandle();
}
main();
我正在尝试为 React-Native 项目设置持续集成,并 运行 解决端到端测试的一些问题,特别是围绕 Metro 捆绑器。
看来在这种情况下使用react-native脚本并不靠谱:
- iOS 构建自发地在新终端中生成一个捆绑器,并在构建后 运行 保留它。
- Android 构建依赖于必须事先手动启动的 运行ning 实例。
- 除了发送信号(Ctrl+C 或 kill)之外,不能通过其他方式停止捆绑程序。
- 没有与构建同步以确保捆绑程序在应用程序启动时准备好进行处理。
我想编写一个可以启动 Metro 的自定义脚本,运行 服务器准备就绪后进行测试,最后停止服务器以清理环境。
metro 捆绑程序必须 运行 作为一个单独的进程才能处理请求。这样做的方法是使用 Child Process : Spawn 并保持返回的对象正确清理。
这是一个基本脚本,可以同时启动 Metro 和 Gradle,并等待两者根据其日志输出准备就绪。
'use strict';
const cp = require('child_process');
const fs = require('fs');
const readline = require('readline');
// List of sub processes kept for proper cleanup
const children = {};
async function asyncPoint(ms, callback = () => {}) {
return await new Promise(resolve => setTimeout(() => {
resolve(callback());
}, ms));
}
async function fork(name, cmd, args, {readyRegex, timeout} = {}) {
return new Promise((resolve) => {
const close = () => {
delete children[name];
resolve(false);
};
if(timeout) {
setTimeout(() => close, timeout);
}
const child = cp.spawn(
cmd,
args,
{
silent: false,
stdio: [null, 'pipe', 'pipe'],
},
);
child.on('close', close);
child.on('exit', close);
child.on('error', close);
const output = fs.createWriteStream(`./volatile-build-${name}.log`);
const lineCb = (line) => {
console.log(`[${name}] ${line}`);
output.write(line+'\n');
if (readyRegex && line.match(readyRegex)) {
resolve(true);
}
};
readline.createInterface({
input: child.stdout,
}).on('line', lineCb);
readline.createInterface({
input: child.stderr,
}).on('line', lineCb);
children[name] = child;
});
}
async function sighandle() {
console.log('\nClosing...');
Object.values(children).forEach(child => child.kill('SIGTERM'));
await asyncPoint(1000);
process.exit(0);
}
function setSigHandler() {
process.on('SIGINT', sighandle);
process.on('SIGTERM', sighandle);
}
async function main() {
setSigHandler();
// Metro Bundler
const metroSync = fork(
'metro',
process.argv0,
[ // args
'./node_modules/react-native/local-cli/cli.js',
'start',
],
{ // options
readyRegex: /Loading dependency graph, done./,
timeout: 60000,
}
);
// Build APK
const buildSync = fork(
'gradle',
'./android/gradlew',
[ // args
`--project-dir=${__dirname}/android`,
'assembleDebug',
],
{ // options
readyRegex: /BUILD SUCCESSFUL/,
timeout: 300000,
}
);
if (await metroSync && await buildSync) {
// TODO: Run tests here
}
sighandle();
}
main();