React Native && Expo框架实战应用

React Native && Expo框架整理

React Native项目支持的项目配置插件

一、配置文件(如 .babelrc 或 babel.config.js)

presets: [“module:metro-react-native-babel-preset”]

  1. @babel/plugin-proposal-export-namespace-from: Babel 插件,用于将 ES6 模块中的星号导出(export * from ‘module’)转换为 ES5 代码
  2. @babel/plugin-proposal-decorators: 开启装饰者模式
  3. @babel/plugin-proposal-optional-catch-binding: 在进行异常捕获时,有时我们并不想强制要求捕获到的异常必须被绑定到一个变量中,而是希望在有异常时只执行一些操作,而不必在意异常本身的信息
  4. transform-inline-environment-variables: RN配置环境变量
  5. @babel/plugin-proposal-export-namespace-from: 用于将 ES6 模块中的星号导出(export * from ‘module’)转换为 ES5 代码
  6. react-native-reanimated/plugin: 使用react-native-reanimated库需要引用这个babel库
  7. transform-remove-console: 优化性能,删除掉代码中的console,可以配置: [‘transform-remove-console’, { exclude: [‘tron’, ‘info’, ‘error’] }]
  8. module-resolver: 配置alias
[
	'module-resolver',
	{
		extensions: ['.ios.js', '.android.js', '.js', '.jsx', '.ts', '.tsx', '.json'],
		alias: {
			'@app': './app/',
			'@@': './',
		},
	},
]

二、拆包分包打包

metro.business.config.js

const fs = require('fs')
const { argv } = require('process')
const platformIndex = argv.indexOf('--platform')
let platformVal = platformIndex !== -1 && argv[platformIndex + 1] ? argv[platformIndex + 1] : null
console.log('[metro.business.config] platformVal:', platformVal)
// 读取 moduleIdList.txt,转换为数组
const moduleIdList = fs
	.readFileSync(`./build-dist/common/${platformVal}/moduleIdList.txt`, 'utf8')
	.toString()
	.split('\n')

function getModuleId(path) {
	// 根据文件的相对路径构建 moduleId
	const projectRootPath = __dirname
	let moduleId = path.substr(projectRootPath.length + 1)
	return moduleId
}

function createModuleIdFactory() {
	return getModuleId
}

function processModuleFilter(module) {
	//const modulePath = getProjectPath(module['path'])
	const moduleId = getModuleId(module['path'])
	if (moduleIdList.indexOf(moduleId) >= 0) {
		// 过滤掉上一步记录的基础模块的moduleId
		return false
	}
	return true
}

module.exports = {
	serializer: {
		createModuleIdFactory,
		processModuleFilter,
	},
}

metro.common.config.js
通常将版本稳定不变的包放到common包中(react、react-native)

// 为了避免 moduleId 重复,目前业内主流的做法是把模块的文件路径当作 moduleId。metro 暴露了 createModuleIdFactory() 这个函数,可以在这个函数里自定义moduleId 的生成逻辑。
const fs = require('fs')
const { argv } = require('process')
const platformIndex = argv.indexOf('--platform')
let platformVal = platformIndex !== -1 && argv[platformIndex + 1] ? argv[platformIndex + 1] : null
console.log('[metro.common.config] platformVal:', platformVal)
function getModuleId(path) {
	// 根据文件的相对路径构建 moduleId
	const projectRootPath = __dirname
	let moduleId = path.substr(projectRootPath.length + 1)
	return moduleId
}

function createModuleIdFactory() {
	return getModuleId
}

function processModuleFilter(module) {
	const moduleId = getModuleId(module['path'])
	// 把 moduleId 写入 moduleIdList.txt 文件,记录基础模块的moduleId
	fs.appendFileSync(`./build-dist/common/${platformVal}/moduleIdList.txt`, `${moduleId}\n`)
	return true
}

module.exports = {
	serializer: {
		createModuleIdFactory,
		processModuleFilter,
	},
}

三、打包命令

“build”: “node build.js --hbc-mode --linux --env=fat --business-name myAppName --common --business-entry index.js”

build.js
/**
 * 构建包
 * @param --common 构建公共包
 * @param --business-name {String} 业务名称
 * @param --business-entry {String} 业务主入口,默认:index.js
 * @example 仅打业务包: node build.js --business-name fund-index --business-entry index.js
 * @example 打公共包+业务包: node build.js --business-name fund-index --common --business-entry index.js
 * @example 仅打公共包: node build.js --common
 */
const AdmZip = require('adm-zip')
var child_process = require('child_process')
const path = require('path')
const fs = require('fs')
const { argv } = require('process')
const cwdPath = process.cwd()
console.log('[build] cwdPath:', cwdPath)
const commonIndex = argv.indexOf('--common')
const busNameIndex = argv.indexOf('--business-name')
const isLinux = argv.indexOf('--linux') > -1
const isHbcMode = argv.indexOf('--hbc-mode') > -1
const isFat = argv.indexOf('--env=fat') > -1
const bundleFilePrefix = isHbcMode ? '-hbc' : ''
let busNameVal = busNameIndex !== -1 && argv[busNameIndex + 1] ? argv[busNameIndex + 1] : null
console.log('[build] busNameVal:', busNameVal)

const busEntryIndex = argv.indexOf('--business-entry')
let busEntryVal =
	busEntryIndex !== -1 && argv[busEntryIndex + 1] ? argv[busEntryIndex + 1] : 'index.js'
console.log('[build] busEntryVal:', busEntryVal)
const buildDist = path.join(cwdPath, 'build-dist')

// 清空影响缓存的文件
if (fs.existsSync(buildDist)) {
	fs.rmdirSync(buildDist, { recursive: true, force: true })
}
fs.mkdirSync(buildDist)

if (commonIndex !== -1) {
	console.log('[build] start build [common] ...')
	fs.mkdirSync(path.join(buildDist, 'common'))
	fs.mkdirSync(path.join(buildDist, 'common', 'ios'))
	fs.mkdirSync(path.join(buildDist, 'common', 'android'))

	child_process.execSync(
		'npx react-native  bundle --platform ios --config metro.common.config.js --dev false --entry-file metro.common.js --bundle-output build-dist/common/ios/index.bundle --assets-dest build-dist/common/ios',
		{ cwd: cwdPath },
	)
	child_process.execSync(
		'npx react-native bundle --platform android --config metro.common.config.js --dev false --entry-file metro.common.js --bundle-output build-dist/common/android/index.bundle --assets-dest build-dist/common/android',
		{ cwd: cwdPath },
	)
	if (isHbcMode) {
		fs.mkdirSync(path.join(buildDist, 'common', 'ios' + bundleFilePrefix))
		fs.mkdirSync(path.join(buildDist, 'common', 'android' + bundleFilePrefix))
		if (isLinux) {
			console.log('[build] build [common:hbc] by linux system')
			child_process.execSync(
				'./node_modules/hermes-engine/linux64-bin/hermesc -emit-binary -out ./build-dist/common/android-hbc/index.bundle -output-source-map ./build-dist/common/android/index.bundle',
				{ cwd: cwdPath },
			)
			child_process.execSync(
				'./node_modules/hermes-engine/linux64-bin/hermesc -emit-binary -out ./build-dist/common/ios-hbc/index.bundle -output-source-map ./build-dist/common/ios/index.bundle',
				{ cwd: cwdPath },
			)
		} else {
			child_process.execSync(
				'./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out ./build-dist/common/android-hbc/index.bundle -output-source-map ./build-dist/common/android/index.bundle',
				{ cwd: cwdPath },
			)
			child_process.execSync(
				'./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out ./build-dist/common/ios-hbc/index.bundle -output-source-map ./build-dist/common/ios/index.bundle',
				{ cwd: cwdPath },
			)
		}
		console.log('[build] build [common:hbc] complete')
	}
	const rnCommonZip = new AdmZip()
	const iosCommonZip = new AdmZip()
	const androidCommonZip = new AdmZip()
	const webCommonZip = new AdmZip()

	androidCommonZip.addLocalFile(
		path.join(buildDist, 'common', 'android' + bundleFilePrefix, 'index.bundle'),
	)
	iosCommonZip.addLocalFile(
		path.join(buildDist, 'common', 'ios' + bundleFilePrefix, 'index.bundle'),
	)

	rnCommonZip.addFile('ios.zip', iosCommonZip.toBuffer())
	rnCommonZip.addFile('android.zip', androidCommonZip.toBuffer())
	rnCommonZip.addFile('web.zip', webCommonZip.toBuffer())
	rnCommonZip.writeZip(path.join(buildDist, 'rn-common.zip'))
	console.log('[build] build [common] complete')
}

if (busNameVal) {
	console.log('[build] start build [business] ...')
	fs.mkdirSync(path.join(buildDist, 'business'))
	fs.mkdirSync(path.join(buildDist, 'business', 'ios'))
	fs.mkdirSync(path.join(buildDist, 'business', 'android'))
	// 修改入口文件的注册模块名称为业务包名称
	let entryFilePath = path.join(cwdPath, busEntryVal)
	// 读取入口文件内容,并替换注册的模块名称
	let newEntryFileContent = fs
		.readFileSync(entryFilePath)
		.toString()
		.replace(
			/AppRegistry\.registerComponent\((['"])[^'"]+\1/,
			`AppRegistry.registerComponent($1${busNameVal}$1`,
		)
	let newEntryFilePath = path.join(cwdPath, busEntryVal.replace(/\.js$/, '.tmp.js'))
	// 创建临时主入口文件,并写入新的文件内容(替换模块名称后的内容)
	fs.writeFileSync(newEntryFilePath, newEntryFileContent)
	child_process.execSync(
		`ENV=${
			isFat ? 'fat' : 'prd'
		} npx react-native bundle --platform ios --config metro.business.config.js --dev false --entry-file ${newEntryFilePath} --bundle-output build-dist/business/ios/index.bundle --assets-dest build-dist/business/ios`,
		{ cwd: cwdPath },
	)
	child_process.execSync(
		`ENV=${
			isFat ? 'fat' : 'prd'
		} npx react-native bundle --platform android --config metro.business.config.js --dev false --entry-file ${newEntryFilePath} --bundle-output build-dist/business/android/index.bundle --assets-dest build-dist/business/android`,
		{ cwd: cwdPath },
	)
	if (isHbcMode) {
		fs.mkdirSync(path.join(buildDist, 'business', 'ios' + bundleFilePrefix))
		fs.mkdirSync(path.join(buildDist, 'business', 'android' + bundleFilePrefix))
		if (isLinux) {
			console.log('[build] build [business:hbc] by linux system')
			child_process.execSync(
				'./node_modules/hermes-engine/linux64-bin/hermesc -emit-binary -out ./build-dist/business/ios-hbc/index.bundle -output-source-map ./build-dist/business/ios/index.bundle',
				{ cwd: cwdPath, maxBuffer: 2048 * 2048 },
			)
			child_process.execSync(
				'./node_modules/hermes-engine/linux64-bin/hermesc -emit-binary -out ./build-dist/business/android-hbc/index.bundle -output-source-map ./build-dist/business/android/index.bundle',
				{ cwd: cwdPath, maxBuffer: 2048 * 2048 },
			)
		} else {
			child_process.execSync(
				'./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out ./build-dist/business/android-hbc/index.bundle -output-source-map ./build-dist/business/android/index.bundle',
				{ cwd: cwdPath },
			)
			child_process.execSync(
				'./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out ./build-dist/business/ios-hbc/index.bundle -output-source-map ./build-dist/business/ios/index.bundle',
				{ cwd: cwdPath },
			)
		}
		console.log('[build] build [business:hbc] complete')
	}
	// 删除临时主入口文件
	fs.rmSync(newEntryFilePath, { recursive: true, force: true })
	console.log('[build] start build [web] ...')
	child_process.execSync(`ENV=${isFat ? 'fat' : 'prd'} npx expo build:web --no-pwa`, {
		cwd: cwdPath,
	})
	console.log('[build] build [web] complete')

	const webBuild = path.join(cwdPath, 'web-build')
	console.log('[build] webBuild:', webBuild)

	const rnBusZip = new AdmZip()
	const webBusZip = new AdmZip()
	const iosBusZip = new AdmZip()
	const androidBusZip = new AdmZip()

	androidBusZip.addLocalFile(
		path.join(buildDist, 'business', 'android' + bundleFilePrefix, 'index.bundle'),
	)
	iosBusZip.addLocalFile(path.join(buildDist, 'business', 'ios' + bundleFilePrefix, 'index.bundle'))
	webBusZip.addLocalFolder(webBuild)
	webBusZip.writeZip('web.zip')

	rnBusZip.addFile('ios.zip', iosBusZip.toBuffer())
	rnBusZip.addFile('android.zip', androidBusZip.toBuffer())
	rnBusZip.addFile('web.zip', webBusZip.toBuffer())

	rnBusZip.writeZip(path.join(buildDist, `rn-${busNameVal}.zip`))
	webBusZip.writeZip(path.join(buildDist, 'web.zip'))
	iosBusZip.writeZip(path.join(buildDist, 'ios.zip'))
	androidBusZip.writeZip(path.join(buildDist, 'android.zip'))
	// 删除临时web包
	fs.rmSync(path.join(cwdPath, 'web.zip'), { recursive: true, force: true })
	console.log('[build] build [business] complete')
} else {
	console.log('[build] no business')
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值