# ReactNative依赖解析流程
导语: 回到上一章节react-native bundle 到 bundle 生成到底发生了什么(metro 打包流程简析) (opens new window),我们知道 RN 构建过程大致分为
解析
,转换
和生成
三个步骤,其中有一个比较关键的点在于,我们需要利用每个module
的依赖关系,进行性能优化,剔除不必要的module
那么react native
是如何获取到每个文件的的依赖 module
呢? 下面我们具体分析一波。
# 一、前言
还记得之前我们讲过 metro
是利用jest-haste-map
去做文件解析,每一个 module 经过 jest-haste-map
之后的数据结构如下:
[
"App.js", // 文件路径,相对于根目录
[
"", // id
1606548079450, // mtime,时间戳
2794, // 文件大小size
1, // visited 0 | 1,
"", // dependencies, 其实返回的并没有文件对应的依赖。
"552e453f03517dc010656369ea82f6a9b6b0383b" // sha1
]
],
然而真实转换后可以得到文件的依赖关系如下:
// 以index.js文件为例
Map {
'/Users/alexganggao/Desktop/rn-jest/MyRN/index.js' => { inverseDependencies: Set {},
path: '/Users/alexganggao/Desktop/rn-jest/MyRN/index.js',
dependencies:
Map {
'@babel/runtime/helpers/interopRequireDefault' => [Object],
'react-native' => [Object],
'./App' => [Object],
'./app.json' => [Object] },
getSource: [Function: getSource],
output: [ [Object] ] },
}
那么再真实的情况下,metro
是如何知道每个 module
有哪些依赖文件呢?
答案其实很简单: 利用 AST
分析得到每个 module
使用了哪些 import
和 require
# 二、metro 依赖时序图
为了验证此设想,笔者分析源码得知,整个 rn 依赖解析时序图下图所示:
对源码有兴趣的笔者,可以查阅下面一部分源码阅读,您也可以直接跳转到这里查看具体依赖分析部分。
# 1. 源码阅读
在进行图谱生成完成之后,利用 /node_modules/metro/src/DeltaBundler/DeltaCalculator.js
初始化了 dependencies 并使用getDelta
进行依赖解析:
this._graph = {
dependencies: new Map(),
entryPoints,
importBundleNames: new Set(),
};
getDelta -> _getChangedDependencies -> initialTraverseDependencies -> _traverseDependenciesForSingleFile -> processModule ->
const internalOptions = getInternalOptions(options);
yield Promise.all(
graph.entryPoints.map(path =>
{
return traverseDependenciesForSingleFile(path, graph, delta, internalOptions)
}
)
);
// 使用options.transform进行转换 options从哪里?其实就是上面的internalOptions,而internalOptions又使用的options
const result = yield options.transform(path); // Get the absolute path of all sub-dependencies (some of them could have been
现在我们将目光聚焦在 options 从哪里得来,利用反向查找 options:
initialTraverseDependencies -> DeltaBundler.buildGraph(entryPoints, options)
// node_modules/metro/src/DeltaBundler.js
buildGraph(entryPoints, options) {
var _this = this;
return _asyncToGenerator(function*() {
const depGraph = yield _this._bundler.getDependencyGraph();
const deltaCalculator = new DeltaCalculator(
entryPoints,
depGraph,
options
);
yield deltaCalculator.getDelta({
reset: true,
shallow: options.shallow
});
const graph = deltaCalculator.getGraph();
_this._deltaCalculators.set(graph, deltaCalculator);
return graph;
})();
}
//node_modules/metro/src/IncrementalBundler.js
// buildGraphForEntries
const graph = yield _this._deltaBundler.buildGraph(absoluteEntryFiles, {
resolve: yield transformHelpers.getResolveDependencyFn(
_this._bundler,
transformOptions.platform
),
transform: yield transformHelpers.getTransformFn(
absoluteEntryFiles,
_this._bundler,
_this._deltaBundler,
_this._config,
transformOptions
),
onProgress: otherOptions.onProgress,
experimentalImportBundleSupport:
_this._config.transformer.experimentalImportBundleSupport,
shallow: otherOptions.shallow
});
// node_modules/metro/src/lib/transformHelpers.js 转换来源于Bundler
// _getTransformFn
return async (path: string) => {
return await bundler.transformFile(path, {
...transformOptions,
type: getType(transformOptions.type, path, config.resolver.assetExts),
inlineRequires: removeInlineRequiresBlacklistFromOptions(
path,
inlineRequires,
),
});
};
// node_modules/metro/src/Bundler.js
class Bundler {
_depGraphPromise: Promise<DependencyGraph>;
_transformer: Transformer;
constructor(config: ConfigT, options?: BundlerOptions) {
this._depGraphPromise = DependencyGraph.load(config, options);
this._depGraphPromise
.then((dependencyGraph: DependencyGraph) => {
this._transformer = new Transformer(
config,
dependencyGraph.getSha1.bind(dependencyGraph),
);
})
.catch(error => {
console.error('Failed to construct transformer: ', error);
});
}
async transformFile(
filePath: string,
transformOptions: TransformOptions,
): Promise<TransformResultWithSource<>> {
// We need to be sure that the DependencyGraph has been initialized.
// TODO: Remove this ugly hack!
await this._depGraphPromise;
return this._transformer.transformFile(filePath, transformOptions);
}
}
// 转换 node_modules/metro/src/DeltaBundler/Transformer.js
async transformFile(
filePath: string,
transformerOptions: TransformOptions,
): Promise<TransformResultWithSource<>> {
const cache = this._cache;
const {
customTransformOptions,
dev,
experimentalImportSupport,
hot,
inlinePlatform,
inlineRequires,
minify,
unstable_disableES6Transforms,
platform,
type,
...extra
} = transformerOptions;
for (const key in extra) {
if (hasOwnProperty.call(extra, key)) {
throw new Error(
'Extra keys detected: ' + Object.keys(extra).join(', '),
);
}
}
const localPath = path.relative(this._config.projectRoot, filePath);
const partialKey = stableHash([
// This is the hash related to the global Bundler config.
this._baseHash,
// Path.
localPath,
customTransformOptions,
dev,
experimentalImportSupport,
hot,
inlinePlatform,
inlineRequires,
minify,
unstable_disableES6Transforms,
platform,
type,
]);
const sha1 = this._getSha1(filePath);
let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, 'hex')]);
const result = await cache.get(fullKey);
// A valid result from the cache is used directly; otherwise we call into
// the transformer to computed the corresponding result.
const data = result
? {result, sha1}
: await this._workerFarm.transform(localPath, transformerOptions);
// Only re-compute the full key if the SHA-1 changed. This is because
// references are used by the cache implementation in a weak map to keep
// track of the cache that returned the result.
if (sha1 !== data.sha1) {
fullKey = Buffer.concat([partialKey, Buffer.from(data.sha1, 'hex')]);
}
cache.set(fullKey, data.result);
return {
...data.result,
getSource(): Buffer {
return fs.readFileSync(filePath);
},
};
}
打印 data.result 得知对于每个文件生成如下数据结构:
{
dependencies:
[ { name: '@babel/runtime/helpers/interopRequireDefault',
data: [Object] },
{ name: 'react-native', data: [Object] },
{ name: './App', data: [Object] },
{ name: './app.json', data: [Object] } ],
output: [ { data: [Object], type: 'js/module' } ]
}
接着看,文件转换:
// node_modules/metro/src/DeltaBundler/WorkerFarm.js
async transform(
filename: string,
options: TransformOptions,
): Promise<TransformerResult> {
try {
const data = await this._worker.transform(
filename,
options,
this._config.projectRoot,
this._transformerConfig,
);
return {
result: data.result,
sha1: data.sha1,
};
} catch (err) {
if (err.loc) {
throw this._formatBabelError(err, filename);
} else {
throw this._formatGenericError(err, filename);
}
}
}
// worker 其实是一个JestWorker,利用JestWorker执行自定义方法
const worker = this._makeFarm(
this._config.transformer.workerPath, // workerPath: 'metro/src/DeltaBundler/Worker',
["transform"],
this._config.maxWorkers
);
转换相关配置信息如下:
{
transformer:
{ assetPlugins: [],
asyncRequireModulePath: 'metro/src/lib/bundle-modules/asyncRequire',
assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry',
babelTransformerPath:
'/Users/alexganggao/Desktop/rn-jest/MyRN/node_modules/@react-native-community/cli/node_modules/metro-react-native-babel-transformer/src/index.js',
dynamicDepsInPackages: 'throwAtRuntime',
enableBabelRCLookup: true,
enableBabelRuntime: true,
experimentalImportBundleSupport: false,
getTransformOptions: [AsyncFunction: getTransformOptions],
minifierConfig:
{ mangle: [Object],
output: [Object],
sourceMap: [Object],
toplevel: false,
compress: [Object] },
minifierPath: 'metro-minify-uglify',
optimizationSizeLimit: 153600,
postMinifyProcess: [Function: postMinifyProcess],
transformVariants: { default: {} },
workerPath: 'metro/src/DeltaBundler/Worker',
publicPath: '/assets' },
cacheStores:
[ FileStore {
_root:
'/var/folders/q_/vy78r8wn5n1cdftb86yqtz280000gn/T/metro-cache' } ],
cacheVersion: '1.0',
projectRoot: '/Users/alexganggao/Desktop/rn-jest/MyRN',
stickyWorkers: true,
watchFolders: [ '/Users/alexganggao/Desktop/rn-jest/MyRN' ],
transformerPath:
'/Users/alexganggao/Desktop/rn-jest/MyRN/node_modules/metro/src/JSTransformer/worker.js',
maxWorkers: 8,
resetCache: false,
reporter:
TerminalReporter {
_activeBundles: Map {},
_scheduleUpdateBundleProgress:
{ [Function: debounced] cancel: [Function: cancel], flush: [Function: flush] },
terminal:
Terminal {
_logLines: [],
_nextStatusStr: '',
_scheduleUpdate: [Function],
_statusStr: '',
_stream: [WriteStream] } } } '00000000'
calcTransformerOptions: { customTransformOptions: [Object: null prototype] {},
dev: false,
hot: false,
inlineRequires: false,
inlinePlatform: true,
minify: true,
platform: 'ios',
experimentalImportSupport: false,
unstable_disableES6Transforms: false,
type: 'module'
}
继续查看 Worker.js
// node_modules/metro/src/DeltaBundler/Worker.js
async function transform(
filename: string,
transformOptions: JsTransformOptions,
projectRoot: string,
transformerConfig: TransformerConfig
): Promise<Data> {
const transformer = getTransformer(projectRoot, transformerConfig);
const transformFileStartLogEntry = {
action_name: "Transforming file",
action_phase: "start",
file_name: filename,
log_entry_label: "Transforming file",
start_timestamp: process.hrtime(),
};
const data = fs.readFileSync(path.resolve(projectRoot, filename));
const sha1 = crypto
.createHash("sha1")
.update(data)
.digest("hex");
const result = await transformer.transform(filename, data, transformOptions);
return {
result,
sha1,
transformFileStartLogEntry,
transformFileEndLogEntry,
};
}
// 查看 getTransformer得知转换使用的是transformerPath
const Transformer = require(transformerPath);
transformers[transformerKey] = new Transformer(projectRoot, transformerConfig);
return transformers[transformerKey];
而我们在上面的转换配置信息中可以得知:transformerPath为'node_modules/metro/src/JSTransformer/worker.js'
至此我们得知 react-native 使用的是JSTransformer
进行代码转换
// node_modules/metro/src/JSTransformer/worker.js
async transform(
filename: string,
data: Buffer,
options: JsTransformOptions,
): Promise<Result> {
const sourceCode = data.toString('utf8');
let type = 'js/module';
if (options.type === 'asset') {
type = 'js/module/asset';
}
if (options.type === 'script') {
type = 'js/script';
}
// json返回依赖为空
if (filename.endsWith('.json')) {
let code = JsFileWrapping.wrapJson(sourceCode);
let map = [];
if (options.minify) {
({map, code} = await this._minifyCode(filename, code, sourceCode, map));
}
return {
dependencies: [],
output: [
{
data: {code, lineCount: countLines(code), map, functionMap: null},
type,
},
],
};
}
// $FlowFixMe TODO t26372934 Plugin system
// '/node_modules/@react-native-community/cli/node_modules/metro-react-native-babel-transformer/src/index.js'
const transformer: Transformer<*> = require(this._config
.babelTransformerPath);
const transformerArgs = {
filename,
options: {
...options,
enableBabelRCLookup: this._config.enableBabelRCLookup,
enableBabelRuntime: this._config.enableBabelRuntime,
// Inline requires are now performed at a secondary step. We cannot
// unfortunately remove it from the internal transformer, since this one
// is used by other tooling, and this would affect it.
inlineRequires: false,
projectRoot: this._projectRoot,
publicPath: this._config.publicPath,
},
plugins: [],
src: sourceCode,
};
const transformResult =
type === 'js/module/asset'
? {
...(await assetTransformer.transform(
transformerArgs,
this._config.assetRegistryPath,
this._config.assetPlugins,
)),
functionMap: null,
}
: await transformer.transform(transformerArgs);
// Transformers can ouptut null ASTs (if they ignore the file). In that case
// we need to parse the module source code to get their AST.
let ast =
transformResult.ast ||
babylon.parse(sourceCode, {sourceType: 'unambiguous'});
const {importDefault, importAll} = generateImportNames(ast);
// Add "use strict" if the file was parsed as a module, and the directive did
// not exist yet.
const {directives} = ast.program;
if (
ast.program.sourceType === 'module' &&
directives.findIndex(d => d.value.value === 'use strict') === -1
) {
directives.push(types.directive(types.directiveLiteral('use strict')));
}
// Perform the import-export transform (in case it's still needed), then
// fold requires and perform constant folding (if in dev).
const plugins = [];
const opts = {
...options,
inlineableCalls: [importDefault, importAll],
importDefault,
importAll,
};
if (options.experimentalImportSupport) {
plugins.push([importExportPlugin, opts]);
}
if (options.inlineRequires) {
plugins.push([inlineRequiresPlugin, opts]);
}
if (!options.dev) {
plugins.push([constantFoldingPlugin, opts]);
}
plugins.push([inlinePlugin, opts]);
({ast} = transformFromAstSync(ast, '', {
ast: true,
babelrc: false,
code: false,
configFile: false,
comments: false,
compact: false,
filename,
plugins,
sourceMaps: false,
}));
let dependencyMapName = '';
let dependencies;
let wrappedAst;
// If the module to transform is a script (meaning that is not part of the
// dependency graph and it code will just be prepended to the bundle modules),
// we need to wrap it differently than a commonJS module (also, scripts do
// not have dependencies).
if (type === 'js/script') {
dependencies = [];
wrappedAst = JsFileWrapping.wrapPolyfill(ast);
} else {
try {
const opts = {
asyncRequireModulePath: this._config.asyncRequireModulePath,
dynamicRequires: getDynamicDepsBehavior(
this._config.dynamicDepsInPackages,
filename,
),
inlineableCalls: [importDefault, importAll],
keepRequireNames: options.dev,
};
({ast, dependencies, dependencyMapName} = collectDependencies(
ast,
opts,
)); // 依赖收集
} catch (error) {
if (error instanceof collectDependencies.InvalidRequireCallError) {
throw new InvalidRequireCallError(error, filename);
}
throw error;
}
// 处理,globalThis,_d,polyfill,替换require部分
({ast: wrappedAst} = JsFileWrapping.wrapModule(
ast,
importDefault,
importAll,
dependencyMapName,
));
}
const reserved =
options.minify && data.length <= this._config.optimizationSizeLimit
? normalizePseudoglobals(wrappedAst)
: [];
// 代码生成
const result = generate(
wrappedAst,
{
comments: false,
compact: false,
filename,
retainLines: false,
sourceFileName: filename,
sourceMaps: true,
},
sourceCode,
);
let map = result.rawMappings ? result.rawMappings.map(toSegmentTuple) : [];
let code = result.code;
if (options.minify) {
// 压缩代码
({map, code} = await this._minifyCode(
filename,
result.code,
sourceCode,
map,
reserved,
));
}
const {functionMap} = transformResult;
return {
dependencies,
output: [
{data: {code, lineCount: countLines(code), map, functionMap}, type},
],
};
}
查阅如上代码,我们得知:
- js 转换使用的是
metro-react-native-babel-transformer
对 js 代码进行转换 - 针对 dependencies,主要使用了
collectDependencies
方法:
# 2. collectDependencies 依赖收集
下面我们具体来看一下是如何进行依赖收集的:
// node_modules/metro/src/ModuleGraph/worker/collectDependencies.js
const traverse = require("@babel/traverse").default;
const types = require("@babel/types");
import type { Ast } from "@babel/core";
// ...
export type DynamicRequiresBehavior = "throwAtRuntime" | "reject";
/**
* Produces a Babel template that will throw at runtime when the require call
* is reached. This makes dynamic require errors catchable by libraries that
* want to use them.
*/
const dynamicRequireErrorTemplate = template(`
(function(line) {
throw new Error(
'Dynamic require defined at line ' + line + '; not supported by Metro',
);
})(LINE)
`);
/**
* Produces a Babel template that transforms an "import(...)" call into a
* "require(...)" call to the asyncRequire specified.
*/
const makeAsyncRequireTemplate = template(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, MODULE_NAME)
`);
const makeAsyncPrefetchTemplate = template(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, MODULE_NAME)
`);
const makeJSResourceTemplate = template(`
require(ASYNC_REQUIRE_MODULE_PATH).resource(MODULE_ID, MODULE_NAME)
`);
function collectDependencies(
ast: Ast,
options: Options
): CollectedDependencies {
const visited = new WeakSet();
const state: State = {
asyncRequireModulePathStringLiteral: null,
dependency: 0,
dependencyCalls: new Set(),
dependencyData: new Map(),
dependencyIndexes: new Map(),
dependencyMapIdentifier: null,
dynamicRequires: options.dynamicRequires,
keepRequireNames: options.keepRequireNames,
disableRequiresTransform: !!options.disableRequiresTransform,
};
const visitor = {
// 处理所有AST中的表达式,如果call是import类型的,其arguments一定是Expression
CallExpression(path: Path, state: State) {
if (visited.has(path.node)) {
return;
}
const callee = path.get("callee");
const name = callee.node.name;
// 如果是import语句,执行依赖手机
if (callee.isImport()) {
processImportCall(path, state, {
prefetchOnly: false,
});
return;
}
// 如果是import语句,执行依赖收集
if (name === "__prefetchImport" && !path.scope.getBinding(name)) {
processImportCall(path, state, {
prefetchOnly: true,
});
return;
}
// 如果为js资源或者分包资源同样处理import,但是生成的requore将会不同, tips: 似乎__jsResource并没有被使用过
if (
(name === "__jsResource" ||
name === "__conditionallySplitJSResource") &&
!path.scope.getBinding(name)
) {
processImportCall(path, state, {
prefetchOnly: false,
jsResource: true,
});
return;
}
if (state.dependencyCalls.has(name) && !path.scope.getBinding(name)) {
visited.add(processRequireCall(path, state).node);
}
},
ImportDeclaration(path: Path, state: State) {
// 针对import语句,记录import的module到一个map中,如果module被记录过那么不会重复记录
const dep = getDependency(state, path.node.source.value, {
prefetchOnly: false,
});
dep.data.isAsync = false;
},
Program(path: Path, state: State) {
state.asyncRequireModulePathStringLiteral = types.stringLiteral(
options.asyncRequireModulePath
);
state.dependencyMapIdentifier = path.scope.generateUidIdentifier(
"dependencyMap"
);
state.dependencyCalls = new Set(["require", ...options.inlineableCalls]);
},
};
traverse(ast, visitor, null, state);
// Compute the list of dependencies.
const dependencies = new Array(state.dependency);
for (const [name, data] of state.dependencyData) {
dependencies[nullthrows(state.dependencyIndexes.get(name))] = {
name,
data,
};
}
return {
ast,
dependencies,
dependencyMapName: nullthrows(state.dependencyMapIdentifier).name,
};
}
// 处理import语句及js资源
function processImportCall(
path: Path,
state: State,
options: DepOptions
): Path {
// 获取module 名称
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
// 记录module依赖,如果当前module已经存在map中则返回,否则先记录
const dep = getDependency(state, name, options);
if (!options.prefetchOnly) {
delete dep.data.isPrefetchOnly;
}
if (state.disableRequiresTransform) {
return path;
}
const ASYNC_REQUIRE_MODULE_PATH = state.asyncRequireModulePathStringLiteral;
// 生成零时的moduleId,用于在将代码中的import/require进行替换,
const MODULE_ID = types.memberExpression(
state.dependencyMapIdentifier,
types.numericLiteral(dep.index),
true
);
// module名称
const MODULE_NAME = types.stringLiteral(name);
// 如果是js资源,那么就替换对应的加载路径为指定的module
if (options.jsResource) {
path.replaceWith(
makeJSResourceTemplate({
ASYNC_REQUIRE_MODULE_PATH,
MODULE_ID,
MODULE_NAME,
})
);
} else if (!options.prefetchOnly) {
// 如果为不是预加载则直接将import()转换成require()()语句进行加载
path.replaceWith(
makeAsyncRequireTemplate({
ASYNC_REQUIRE_MODULE_PATH,
MODULE_ID,
MODULE_NAME,
})
);
} else {
// 否则将import转换成require.prefetch的方式加载
path.replaceWith(
makeAsyncPrefetchTemplate({
ASYNC_REQUIRE_MODULE_PATH,
MODULE_ID,
MODULE_NAME,
})
);
}
return path;
}
// 处理require语句
function processRequireCall(path: Path, state: State): Path {
// 获取module 名称
const name = getModuleNameFromCallArgs(path);
if (name == null) {
if (state.dynamicRequires === "reject") {
throw new InvalidRequireCallError(path);
}
path.replaceWith(
dynamicRequireErrorTemplate({
LINE: "" + path.node.loc.start.line,
})
);
return path;
}
// 记录module依赖,如果当前module已经存在map中则返回,否则先记录
const dep = getDependency(state, name, { prefetchOnly: false });
dep.data.isAsync = false;
delete dep.data.isPrefetchOnly;
// 如果不使用require 转换逻辑则直接返回
if (state.disableRequiresTransform) {
return path;
}
// 生成零时的moduleId,用于在将代码中的import/require进行替换,
const moduleIDExpression = types.memberExpression(
state.dependencyMapIdentifier,
types.numericLiteral(dep.index),
true
);
// 替换require内容为对应的id
path.node.arguments = state.keepRequireNames
? [moduleIDExpression, types.stringLiteral(name)]
: [moduleIDExpression];
return path;
}
// 记录依赖,当前module路径是否被记录过,如果没有则记录否则直接返回对呀信息
function getDependency(
state: State,
name: string,
options: DepOptions
): InternalDependencyInfo {
let index = state.dependencyIndexes.get(name);
let data: ?InternalDependencyData = state.dependencyData.get(name);
if (!data) {
index = state.dependency++;
data = { isAsync: true };
if (options.prefetchOnly) {
data.isPrefetchOnly = true;
}
state.dependencyIndexes.set(name, index);
state.dependencyData.set(name, data);
}
return { index: nullthrows(index), data: nullthrows(data) };
}
// 获取require或者import部分具体的内容
function getModuleNameFromCallArgs(path: Path): ?string {
const expectedCount =
path.node.callee.name === "__conditionallySplitJSResource" ? 2 : 1;
if (path.get("arguments").length !== expectedCount) {
throw new InvalidRequireCallError(path);
}
const result = path.get("arguments.0").evaluate();
if (result.confident && typeof result.value === "string") {
return result.value;
}
return null;
}
collectDependencies.getModuleNameFromCallArgs = getModuleNameFromCallArgs;
class InvalidRequireCallError extends Error {
constructor({ node }: any) {
const line = node.loc && node.loc.start && node.loc.start.line;
super(
`Invalid call at line ${line || "<unknown>"}: ${generate(node).code}`
);
}
}
collectDependencies.InvalidRequireCallError = InvalidRequireCallError;
module.exports = collectDependencies;
# 总结
collectDependencies
主要利用 babel 去解析所有的 import 和 require 得到每个文件的依赖 module。处理 import 语句时,会将所有的 import 语句转换为 require 进行加载
// 转换前
import { Image } from "react-native";
// 转换后
var _reactNative = r(d[2]);
其他情况:(tips: 暂未找到具体的实现方式,有发现的同学可以一起交流)
- 对于对于预加载的情况,会转换成
require().prefetch
的方式进行加载; - 如果为资源文件则会转换成
require().resource
的方式加载.
- 在处理 require 语句时, 会将 require 的内容替换成对应的 moduleId
举个例子: 类似于
require('Foo')
将会被转换成require(_depMap[3], 'Foo')
,这个_depMap
是所有的依赖集合,其实在这个时候,我们并不需要准确的知道真实的moduleId
,只需要知道文件的依赖即可。
// 转换前
<Image source={require('./demo.png')}></Image>
// 转换后
_react.default.createElement(_reactNative.Image,{source: r(d[4]) }
- 经过
babel
解析之后的module
依赖如下:
// 经过 collectDependencies得到的依赖
dependencies: [
{ name: "@babel/runtime/helpers/interopRequireDefault", data: [Object] },
{ name: "react-native", data: [Object] },
{ name: "./App", data: [Object] },
{ name: "./app.json", data: [Object] },
];
- js 转换使用的是
metro-react-native-babel-transformer
对 js 代码进行转换
其他:
- 至于如何生成
polyfills
部分,以及\_d
,globalThis
等,有兴趣的同学可以看一下node_modules/metro/src/ModuleGraph/worker/JsFileWrapping.js
- 本文链接: https://mrgaogang.github.io/react/ReactNative%E4%BE%9D%E8%B5%96%E8%A7%A3%E6%9E%90%E6%B5%81%E7%A8%8B.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!