React-360 源码阅读【2】
接着前文 《React-360 源码阅读【1】》,下面主要分析构建这一部分业绩是 /scripts 目录下的内容;
结构
- build-packages.js
- bump-versions.js
- bundle.js
- install-deps.js
install-reps.js
在说这片代码之前,我们需要先去 packages/react-360-cli/src/getPackager.js
了解下如何判断是否支持 yarn。
const child_process = require('child_process');
let packager = null;
function getPackager() {
if (packager !== null) {
return packager;
}
try {
child_process.execSync(
/^win/.test(process.platform) ? 'yarn --version 2> NUL' : 'yarn --version 2>/dev/null'
);
packager = 'yarn';
} catch (e) {
packager = 'npm';
}
return packager;
}
module.exports = getPackager;
实际上是通过 判断 yarn —version 来实现的。
const child_process = require('child_process');
const path = require('path');
const getPackager = require('../packages/react-360-cli/src/getPackager');
/**
* Script to install development dependencies for users building the codebase
* from the GitHub repo.
*/
function installDeps() {
// 大概定义需要构建的包的路径
const paths = {
'react-360': path.resolve(__dirname, '..'),
'react-360-web': path.resolve(__dirname, '..', 'React360'),
};
console.log('Determining packager...');
// 判断使用包的管理器
const packager = getPackager();
if (packager !== 'yarn' && packager !== 'npm') {
throw new Error('Unexpected packager: ' + packager);
}
console.log(`\x1b[34;1m${packager}\x1b[0m detected`);
const args = packager === 'yarn' ? ['--pure-lockfile'] : ['install'];
let promise = Promise.resolve();
Object.keys(paths).forEach(pkg => {
promise = promise.then(() => {
console.log('Installing dependencies for ' + pkg);
return new Promise((resolve, reject) => {
// 调用安装的命令
const proc = child_process.spawn(packager, args, {
stdio: 'inherit',
cwd: paths[pkg],
shell: true,
});
proc.on('close', code => {
if (code !== 0) {
reject({pkg, code});
}
resolve();
});
});
});
});
return promise.then(
() => {
console.log('Completed successfully!');
},
({pkg, code}) => {
console.log(`Dependency installation for ${pkg} failed with code ${code}`);
}
);
}
module.exports = installDeps;
if (require.main === module) {
// Called from the command line directly
installDeps();
}
运行结果如图:
build-packages.js
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
const installDeps = require('./install-deps');
function buildPackage(name, dir) {
console.log(` Packaging \x1b[32m'${name}'\x1b[0m`);
return new Promise((resolve, reject) => {
const json = path.resolve(dir, 'package.json');
fs.stat(json, (err, res) => {
if (err) {
console.log(`\x1b[41;1m${json} does not exist\x1b[0m`);
reject(err);
return;
}
resolve(json);
});
}).then(json => {
// 获取包的 package.json 的最新版本
const version = require(json).version;
return new Promise((resolve, reject) => {
// 使用 pack 命令将文件打包城 tgz 文件
const npm = child_process.spawn('npm', ['pack'], {stdio: 'inherit', cwd: dir, shell: true});
npm.on('close', code => {
if (code !== 0) {
reject(code);
}
resolve(path.resolve(dir, `${name}-${version}.tgz`));
});
});
});
}
// 移动构建后包的位置
function relocatePackage(name, src, dest, rev) {
const destfile = path.join(dest, `${name}-${rev}.tgz`);
console.log(`Moving ${src} to ${destfile}`);
return new Promise((resolve, reject) => {
fs.rename(src, destfile, () => {
resolve();
});
});
}
// 确保该目录存在
function ensureDirectory(dir) {
try {
const stat = fs.statSync(dir);
if (stat) {
return;
}
} catch (e) {
fs.mkdirSync(dir);
}
}
const PACKAGES = {
'react-360': path.resolve(__dirname, '..'),
'react-360-web': path.resolve(__dirname, '..', 'React360'),
};
// 获取最新提交的 commit 字符串
const rev = child_process.execSync('git rev-parse HEAD').toString().trim().substr(0, 8);
const DEST = path.resolve(__dirname, '..', 'package_builds');
installDeps()
.then(() => {
console.log(`\x1b[34;1mBuilding packages at git rev ${rev}...\x1b[0m`);
ensureDirectory(DEST);
return Promise.all(
Object.keys(PACKAGES).map(p =>
// 进行pack然后移动到指定位置
buildPackage(p, PACKAGES[p]).then(src => relocatePackage(p, src, DEST, rev))
)
);
})
.then(() => {
console.log(`\nBuilt all packages! They are located at \x1b[34m${DEST}\x1b[0m`);
});
bump-versions.js
顾名思义,就是用户版本好的迭代的;
const fs = require('fs');
const path = require('path');
// 版本号的正则表达式
const versionExpr = /^\d+\.\d+\.\d+$/;
// 需要替换掉 package.json 里面的 version
// Can also replace strings of the form `"somedependency": "..."` with newer versions
function updateVersion(pkg, version, deps) {
const contents = fs.readFileSync(pkg, 'utf8');
// 替换某个包的的版本号
let updated = contents.replace(/"version":\s*"[^"]+"/, `"version": "${version}"`);
if (deps) {
for (const dep in deps) {
updated = updated.replace(new RegExp(`"${dep}":\\s*"[^"]+"`), `"${dep}": "${deps[dep]}"`);
}
}
// 将修改的好的 package.json 字符串重新写入
fs.writeFileSync(pkg, updated);
}
const args = process.argv;
// 获取命令行的版本号
const version = args[2];
if (typeof version !== 'string' || version.length === 0) {
console.log('\x1b[31;1mMust provide a new version string\x1b[0m');
console.log('Usage: node bump-versions.js VERSION');
console.log(' VERSION - a new version string for packages');
process.exit(1);
}
if (!version.match(versionExpr)) {
console.log('\x1b[31;1mVersion must be in the form `MAJOR.MINOR.PATCH`\x1b[0m');
}
const packages = {
'react-360': path.resolve(__dirname, '..', 'package.json'),
'react-360-web': path.resolve(__dirname, '..', 'React360', 'package.json'),
'react-360-cli': path.resolve(__dirname, '..', 'react-360-cli', 'package.json'),
};
const cliGenerator = path.resolve(
__dirname,
'..',
'react-360-cli',
'generators',
'package.json.generator.js'
);
const currentVersion = require('../package.json').version;
console.log(
`Current version is \x1b[34;1m${currentVersion}\x1b[0m, updating packages to \x1b[34;1m${version}\x1b[0m`
);
// 这里目录出问题了
const cliVersionPieces = require('../react-360-cli/package.json').version
.split('.')
.map(s => parseInt(s, 10));
cliVersionPieces[2]++; // increment patch version of CLI package
console.log('Updating react-360...');
updateVersion(packages['react-360'], version);
console.log('Updating react-360-web...');
updateVersion(packages['react-360-web'], version);
console.log('Updating new project generator...');
updateVersion(cliGenerator, '0.0.1', {
'react-360': '~' + version,
'react-360-web': '~' + version,
});
console.log('Updating react-360-cli...');
updateVersion(packages['react-360-cli'], cliVersionPieces.join('.'));
console.log(`All packages updated to \x1b[34;1m${version}\x1b[0m`);
运行结果如图:
bundle.js
主要用于对项目进行 JS 打包的功能;
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
const args = process.argv.slice(2);
let customAssetRoot = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--assets' && args[i + 1]) {
customAssetRoot = args[i + 1];
if (customAssetRoot[customAssetRoot.length - 1] !== '/') {
customAssetRoot += '/';
}
}
}
function buildScript(root, buildDir, input, output) {
// 需要我们找到 react-native cli 的命令行位置
const cliLocation =
process.env.RN_CLI_LOCATION ||
path.resolve('node_modules', 'react-native', 'local-cli', 'cli.js');
return new Promise((resolve, reject) => {
const npm = child_process.spawn(
/^win/.test(process.platform) ? 'node.exe' : 'node',
[
cliLocation,
'bundle',
'--entry-file',
input,
'--platform',
'vr',
'--bundle-output',
output,
'--dev',
'false',
'--assets-dest',
buildDir,
],
{stdio: 'inherit', cwd: root},
);
npm.on('close', code => {
if (code !== 0) {
reject(code);
}
resolve();
});
});
}
// 更新 html 的 js bundle 的地址
function updateHTML(root, output, assetRoot) {
const indexPath = path.join(root, 'index.html');
let contents = null;
try {
contents = fs.readFileSync(indexPath, 'utf8');
} catch (e) {
return [
'Could not find index.html. You will need to manually update the paths to your bundled JavaScript files',
];
}
const writeErrors = [];
if (contents.indexOf('./client.bundle') < 0) {
writeErrors.push(
'Could not find path to client in index.html. You will need to manually update it.',
);
} else {
contents = contents.replace(
'client.bundle?platform=vr',
'client.bundle.js',
);
}
if (contents.indexOf('index.bundle') < 0) {
writeErrors.push(
'Could not find path to index.js in index.html. You will need to manually update it.',
);
} else {
contents = contents.replace(
/index\.bundle\?platform=vr(&dev=true)?/,
'index.bundle.js',
);
}
if (assetRoot) {
if (contents.indexOf("'static_assets/'") < 0) {
writeErrors.push('Could not find assetRoot');
} else {
contents = contents.replace(
/assetRoot:\s*'static_assets\/'/,
`assetRoot: '${assetRoot}'`,
);
}
}
try {
fs.writeFileSync(path.join(output, 'index.html'), contents, 'utf8');
} catch (e) {
return [`Unable to write index.html to ${output}`];
}
return writeErrors;
}
根据 react-360 的包的以来判断是否在正确的路径进行构建
function hasPackage(dir) {
const packagePath = path.join(dir, 'package.json');
try {
fs.statSync(packagePath);
} catch (e) {
return false;
}
const pkg = require(packagePath);
if (pkg && pkg.dependencies && pkg.dependencies['react-360']) {
return true;
}
return false;
}
// 循环查找正确的包的路径
function findProjectDir(dir) {
while (!hasPackage(dir)) {
const next = path.join(dir, '..');
if (dir === next) {
console.log('Could not find a React 360 project directory');
process.exit(1);
}
dir = path.join(dir, '..');
}
return dir;
}
// Allow overriding the project location with an env variable
const projectDir =
process.env.PROJECT_LOCATION || findProjectDir(process.cwd());
const buildDir = path.join(projectDir, 'build');
new Promise((resolve, reject) => {
try {
const stat = fs.statSync(buildDir);
if (stat.isDirectory()) {
return resolve();
}
} catch (e) {}
fs.mkdir(path.join(projectDir, 'build'), err => {
if (err) {
console.log('Failed to create `build` directory');
return reject(1);
}
resolve();
});
})
.then(() => {
// 需要打包 index.vr 和 client 的文件
return Promise.all([
buildScript(
projectDir,
buildDir,
path.resolve(projectDir, 'index.js'),
path.resolve(projectDir, 'build', 'index.bundle.js'),
),
buildScript(
projectDir,
buildDir,
path.resolve(projectDir, 'client.js'),
path.resolve(projectDir, 'build', 'client.bundle.js'),
),
]);
})
.then(() => {
console.log(
'Production versions of JS were successfully built.' +
'They can be found at ' +
path.resolve(projectDir, 'build'),
);
const errors = updateHTML(projectDir, path.resolve(projectDir, 'build'), customAssetRoot);
if (errors.length) {
errors.forEach(err => console.log(' ** ', err));
}
})
.catch(err => {
console.log(
'An error occurred during the bundling process. Exited with code ' +
err +
'.\nLook at the packager output above to see what went wrong.',
);
process.exit(1);
});
下一篇,我们就进入整体,先剖析Libraries 的内容;