likes
comments
collection
share

React-native 打包速度优化,配合webpack起飞!

作者站长头像
站长
· 阅读数 12

前言

随着RN项目越来越大,打包速度也越来越慢,经常需要十几分钟,有的人的电脑比较垃圾,在本地打包的时候甚至需要花费半个多小时。

在发布生产的时候,时间花费并没有太大影响,如果是在测试阶段,一个简简单单的bug,改完等打包再验收成功,再重新打包,没有个把小时都搞不定,效率太低了。

今天我们一起来一探究竟,看看到底该如何提升rn的打包速度,今天只做工具层面的分析,不涉及应用代码的规范和书写。

打包流程

RN打包,可以分为三个部分

  • metro:使用metro打包rn代码生成bundle.js
  • Android: 将android目录打包为apk
  • Ios: 将ios目录打包为ipa

具体是如何调用的,我们接着看:

metro打包:

首先我们看下metro打包最终的产物:

var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=true,process=this.process||{},__METRO_GLOBAL_PREFIX__='';process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||"development";
// polyfill
(function (global) {
  "use strict";

  global.__r = metroRequire;
  global[__METRO_GLOBAL_PREFIX__ + "__d"] = define;
  global.__c = clear;
  global.__registerSegment = registerSegment;
  var modules = clear();
  var EMPTY = {};
  var _ref = {},
      hasOwnProperty = _ref.hasOwnProperty;

  if (__DEV__) {
    global.$RefreshReg$ = function () {};

    global.$RefreshSig$ = function () {
      return function (type) {
        return type;
      };
    };
  }
 ...
  
  }
})(typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this);
...
// 引入
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
  
},502,[1,2,48],"node_modules/react-native/Libraries/NewAppScreen/components/DebugInstructions.js");
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
 
},503,[1,2,48],"node_modules/react-native/Libraries/NewAppScreen/components/ReloadInstructions.js");
...
__r(93);
__r(0);
react-native bundle

metro的打包流程基本顺序是: resolve --> transform --> serialize 先解析,再转换,最后序列化生成最终代码。

react-native/local-cli/cli.js // react-native 命令入口

React-native 打包速度优化,配合webpack起飞!

最终调用的是

@react-native-community/cli-plugin-metro/src/commands/bundle/bundle.ts

这个文件只是注册bundle的命令,调用却是写在了buildBundle.ts中:

// @ts-ignore - no typed definition for the package
import Server from 'metro/src/Server';
// @ts-ignore - no typed definition for the package
const outputBundle = require('metro/src/shared/output/bundle');
import path from 'path';
import chalk from 'chalk';
import {CommandLineArgs} from './bundleCommandLineArgs';
import type {Config} from '@react-native-community/cli-types';
import saveAssets from './saveAssets';
import {
  default as loadMetroConfig,
  MetroConfig,
} from '../../tools/loadMetroConfig';
import {logger} from '@react-native-community/cli-tools';

interface RequestOptions {
...
}

export interface AssetData {
...
}

async function buildBundle(
  args: CommandLineArgs,
  ctx: Config,
  output: typeof outputBundle = outputBundle,
) {
  const config = await loadMetroConfig(ctx, {
    maxWorkers: args.maxWorkers,
    resetCache: args.resetCache,
    config: args.config,
  });

  return buildBundleWithConfig(args, config, output);
}

/**
 * Create a bundle using a pre-loaded Metro config. The config can be
 * re-used for several bundling calls if multiple platforms are being
 * bundled.
 */
export async function buildBundleWithConfig(
  args: CommandLineArgs,
  config: MetroConfig,
  output: typeof outputBundle = outputBundle,
) {
  if (config.resolver.platforms.indexOf(args.platform) === -1) {
    logger.error(
      `Invalid platform ${
        args.platform ? `"${chalk.bold(args.platform)}" ` : ''
      }selected.`,
    );

    logger.info(
      `Available platforms are: ${config.resolver.platforms
        .map((x) => `"${chalk.bold(x)}"`)
        .join(
          ', ',
        )}. If you are trying to bundle for an out-of-tree platform, it may not be installed.`,
    );

    throw new Error('Bundling failed');
  }

  // This is used by a bazillion of npm modules we don't control so we don't
  // have other choice than defining it as an env variable here.
  process.env.NODE_ENV = args.dev ? 'development' : 'production';

  let sourceMapUrl = args.sourcemapOutput;
  if (sourceMapUrl && !args.sourcemapUseAbsolutePath) {
    sourceMapUrl = path.basename(sourceMapUrl);
  }

  const requestOpts: RequestOptions = {
    entryFile: args.entryFile,
    sourceMapUrl,
    dev: args.dev,
    minify: args.minify !== undefined ? args.minify : !args.dev,
    platform: args.platform,
    unstable_transformProfile: args.unstableTransformProfile,
  };
  const server = new Server(config);

  try {
    const bundle = await output.build(server, requestOpts);

    await output.save(bundle, args, logger.info);

    // Save the assets of the bundle
    const outputAssets: AssetData[] = await server.getAssets({
      ...Server.DEFAULT_BUNDLE_OPTIONS,
      ...requestOpts,
      bundleType: 'todo',
    });

    // When we're done saving bundle output and the assets, we're done.
    return await saveAssets(outputAssets, args.platform, args.assetsDest);
  } finally {
    server.end();
  }
}

export default buildBundle;

buildBundle主要做了:

  • 实例化 metro Server
  • 启动 metro 构建 bundle
  • 处理资源文件

真正的打包bundle是通过output.build方法实现的,定义在metro/src/shared/output/bundle中:

React-native 打包速度优化,配合webpack起飞! packagerClient就是刚才的server,位置在metro/src/Server.js

...
class Server {
  constructor(config: ConfigT, options?: ServerOptions) {
    this._createModuleId = config.serializer.createModuleIdFactory();
    this._bundler = new IncrementalBundler(config, {
      hasReducedPerformance: options && options.hasReducedPerformance,
      watch: options ? options.watch : undefined,
    });
  }
  ...

  async build(options: BundleOptions): Promise<{
    code: string,
    map: string,
    ...
  }> {
    const {
      entryFile,
      graphOptions,
      onProgress,
      serializerOptions,
      transformOptions,
    } = splitBundleOptions(options);

    const {prepend, graph} = await this._bundler.buildGraph(
      entryFile,
      transformOptions,
      {
        onProgress,
        shallow: graphOptions.shallow,
      },
    );

    const entryPoint = this._getEntryPointAbsolutePath(entryFile);

    ...
    if (this._config.serializer.customSerializer) {
      const bundle = await this._config.serializer.customSerializer(
        entryPoint,
        prepend,
        graph,
        bundleOptions,
      );
      if (typeof bundle === 'string') {
        bundleCode = bundle;
      } else {
        bundleCode = bundle.code;
        bundleMap = bundle.map;
      }
    } else {
      bundleCode = bundleToString(
        baseJSBundle(entryPoint, prepend, graph, bundleOptions),
      ).code;
    }
    if (!bundleMap) {
      bundleMap = sourceMapString(
        [...prepend, ...this._getSortedModules(graph)],
        {
          excludeSource: serializerOptions.excludeSource,
          processModuleFilter: this._config.serializer.processModuleFilter,
        },
      );
    }
    return {
      code: bundleCode,
      map: bundleMap,
    };
  }

打包具体执行的就是IncrementalBundlerbuildGraph方法。

async buildGraph(
    entryFile: string,
    transformOptions: TransformInputOptions,
    otherOptions?: OtherOptions = {
      onProgress: null,
      shallow: false,
    },
  ): Promise<{|+graph: OutputGraph, +prepend: $ReadOnlyArray<Module<>>|}> {
    const graph = await this.buildGraphForEntries(
      [entryFile],
      transformOptions,
      otherOptions,
    );

    const {type: _, ...transformOptionsWithoutType} = transformOptions;

    const prepend = await getPrependedScripts(
      this._config,
      transformOptionsWithoutType,
      this._bundler,
      this._deltaBundler,
    );

    return {
      prepend,
      graph,
    };
  }

其中又调用了buildGraphForEntriesgetPrependedScripts这两个方法,一个是生成依赖图谱,一个是获取前置的脚本。

async buildGraphForEntries(
   ...
  ): Promise<OutputGraph> {
    const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles);

    const graph = await this._deltaBundler.buildGraph(absoluteEntryFiles, {
      ...
    });
    ...
    return graph;
  }

又调用了_deltaBundler

//metro/src/DeltaBundler.js
async buildGraph(
    entryPoints: $ReadOnlyArray<string>,
    options: Options<T>,
  ): Promise<Graph<T>> {
    const depGraph = await this._bundler.getDependencyGraph();

    const deltaCalculator = new DeltaCalculator(entryPoints, depGraph, options);

    await deltaCalculator.getDelta({reset: true, shallow: options.shallow});
    const graph = deltaCalculator.getGraph();

    this._deltaCalculators.set(graph, deltaCalculator);
    return graph;
  }

最终调用的是node-haste/DependencyGraph

static async load(
    ...
  ): Promise<DependencyGraph> {
    ...
    const haste = createHasteMap(config, {watch});
    ...
    return new DependencyGraph({
      haste,
      initialHasteFS: hasteFS,
      initialModuleMap: moduleMap,
      config,
    });
  }

最终生成依赖图谱使用的是:

JestHasteMap.create(hasteConfig)

生成的依赖图谱格式是这样的:

{
        dependencies: new Map([
          ['/root/foo', fooModule],
          ['/root/bar', barModule],
        ]),
        entryPoints: ['foo'],
        importBundleNames: new Set(),
      },

我们再看getPrependedScripts

async function getPrependedScripts(
  ...
): Promise<$ReadOnlyArray<Module<>>> {
  ...
  return [
    _getPrelude({
      dev: options.dev,
      globalPrefix: config.transformer.globalPrefix,
    }),
    ...dependencies.values(),
  ];
}
// metro/src/lib/getPreludeCode.js
function getPreludeCode(): string {
  const vars = [
    '__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now()',
    `__DEV__=${String(isDev)}`,
    ...formatExtraVars(extraVars),
    'process=this.process||{}',
    `__METRO_GLOBAL_PREFIX__='${globalPrefix}'`,
  ];
  return `var ${vars.join(',')};${processEnv(
    isDev ? 'development' : 'production',
  )}`;
}

到这里,解析图谱和polyfillv部分就已经弄清楚了,接下来我们看是如何生成最终代码的。

让我们回到metro/src/server看:

if (this._config.serializer.customSerializer) {
      const bundle = await this._config.serializer.customSerializer(
        entryPoint,
        prepend,
        graph,
        bundleOptions,
      );
      if (typeof bundle === 'string') {
        bundleCode = bundle;
      } else {
        bundleCode = bundle.code;
        bundleMap = bundle.map;
      }
    } else {
      bundleCode = bundleToString(
        baseJSBundle(entryPoint, prepend, graph, bundleOptions),
      ).code;
    }

如果没有提供自定义的转换器,就使用bundleToStringbaseJSBundle包装过的函数生成最终的打包结果。

function baseJSBundle(
) {
  ...

  const preCode = processModules(preModules, processModulesOptions)
    .map(([_, code]) => code)
    .join('\n');

  const modules = [...graph.dependencies.values()].sort(
    (a: Module<MixedOutput>, b: Module<MixedOutput>) =>
      options.createModuleId(a.path) - options.createModuleId(b.path),
  );

  const postCode = processModules(
    getAppendScripts(
      entryPoint,
      [...preModules, ...modules],
      ...
    ),
    processModulesOptions,
  )
    .map(([_, code]) => code)
    .join('\n');

  return {
    pre: preCode,
    post: postCode,
    modules: processModules(
      [...graph.dependencies.values()],
      processModulesOptions,
    ).map(([module, code]) => [options.createModuleId(module.path), code]),
  };
}

baseJSBundle最终返回preCode , postCode 和 modules ,对应的分别是var 和 polyfills 部分的代码 , require 部分的代码 ,  _d 部分的代码

function processModules(
  ...
): $ReadOnlyArray<[Module<>, string]> {
  return [...modules]
    .filter(isJsModule)
    .filter(filter)
    .map((module: Module<>) => [
      module,
      wrapModule(module, {
        createModuleId,
        dev,
        projectRoot,
      }),
    ]);
}

processModules经过过滤后,最后映射为_d(factory,moduleId,dependencies)代码。

然后再看bundleToString

function bundleToString(bundle: Bundle): {|
  +code: string,
  +metadata: BundleMetadata,
|} {
  let code = bundle.pre.length > 0 ? bundle.pre + '\n' : '';
  const modules = [];

  const sortedModules = bundle.modules
    .slice()
    // The order of the modules needs to be deterministic in order for source
    // maps to work properly.
    .sort((a: [number, string], b: [number, string]) => a[0] - b[0]);

  for (const [id, moduleCode] of sortedModules) {
    if (moduleCode.length > 0) {
      code += moduleCode + '\n';
    }
    modules.push([id, moduleCode.length]);
  }

  if (bundle.post.length > 0) {
    code += bundle.post;
  } else {
    code = code.slice(0, -1);
  }

  return {
    code,
    metadata: {pre: bundle.pre.length, post: bundle.post.length, modules},
  };
}

主要的作用就是添加空格,合并成一个整体文件。

android打包:
$ cd android
$ ./gradlew assembleRelease

会调用gradle进行打包,其中android/app/build.gradle:

apply from: "../../node_modules/react-native/react.gradle"

其中有这段:

commandLine(*execCommand, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
                "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)

就是用来打包生产bundle.js的。

ios打包:
npx react-native run-ios --configuration Release

或者直接使用xcode运行build。 在ios中的project.pbxproj有一段代码:

/* Begin PBXShellScriptBuildPhase section */
/* Bundle React Native code and images */
...
shellScript = "export NODE_BINARY=node\n#export FORCE_BUNDLING=true\nexport BUNDLE_COMMAND=bundle\n../node_modules/react-native/scripts/react-native-xcode.sh\n";

在ios的build-phase阶段会执行react-native/scripts/react-native-xcode.sh脚本,脚本中有一段代码:

React-native 打包速度优化,配合webpack起飞!

优化方法:

思路手段

js打包速度优化

  1. 工具层面的优化,metro的优化或者替代
  2. 拆包,基础库包和业务包分开,基础包不会经常更新,只编译业务包

避免原生端壳子的无意义打包

由于使用的rn,可以想到,每次我们改动的代码几乎都是js的代码,最终生成jsbundle,而原生端代码几乎很少改动,如果我们可以每次打包的时候判断一下,原生端目录没有变动,就只执行js的打包,而不重新打包app。

那么当资源变动的时候,我们如何热更新资源呢?

一种是原生的热更新技术,比如生成一个patch包,只包含变动的资源文件,然后安装后覆盖即可。另外一种,可以把jsbundle放在远程,每次启动的时候,会检查远程是否有新版本,有则下载到本地覆盖。例如codepush方案,这种方案也有一定的缺陷,可能会违反ios的规则,还需要自建后台等。

加速原生端壳子的打包速度

这种就需要分别对android和ios单独进行优化了,下面会有介绍。

metro缺点

  • 循环引用未优化
  • 无法分包和按需加载
  • 打包速度太慢
  • 无法做treeshaking
  • 无法图片压缩
  • 如何使用webpack打包react-native
  • 增量加载
  • 官方文档过于简陋

如何使用webpack打包RN?

ReactNative运行js的机制

在0.59版本之前React Native使用的基于Bridge的架构方式:

React-native 打包速度优化,配合webpack起飞!

  • JavascriptCore加载并解析jsbundle文件,IOS原生就带有一个JavascriptCore而Android中需要重新加载,所以这也造成了Android的初始化过程会比IOS慢一些。

  • 运行时需要将前端的组件渲染成Native端的视图,在Bridge中也会构造出一个Shadow Tree,然后通过Yoga布局引擎将Flex布局转换为Native的布局,最终交由UIManager在Native端完成组件的渲染。

现在的新架构是这样的:

React-native 打包速度优化,配合webpack起飞!

  • JSI(Javascript Interface):Javascript可以持有C++对象的引用,并调用其方法,同时Native端(Android、IOS)均支持对于C++的支持。从而避免了使用Bridge对JSON的序列化与反序列化,实现了Javascript与Native端直接的通信。

  • Hermes: JSI允许前端使用不同的浏览器引擎,Facebook针对Android 需要加载JavascriptCore的问题,研发了一个更适合Android的开源浏览器引擎Hermes。

  • CodeGen:作为一个工具来自动化的实现Javascript和Native端的兼容性。

  • Fabric:与旧版UIManager作用一致,区别之处在于旧架构下Native端的渲染需要完成一系列的”跨桥“操作,即React -> Native -> Shadow Tree -> Native UI,新的架构下UIManager可以通过C++直接创建Shadow Tree大大提高了用户界面体验的速度。

可以知道,只要是正常的js文件就能够执行,没有多余的操作。

Merto构建

上面我只分析了metro打包生成bundle js流程做了分析,除了这个之外,还有很多的操作,我们可以看到metro的源码目录:

React-native 打包速度优化,配合webpack起飞!

从目录中我们也能看出来,metro还做了这些工作:

  • buck work 工具
  • babel注册、转换、预设
  • 缓存处理
  • hermes 兼容
  • 解析、压缩、运行时环境
  • 热更新、监控、代理

幸运的是,这些工作webpack中大部分都已经自带了,所以我们只需要稍稍改动就可以使用webpack打包我们的react-native代码。

webpack实现
polyfill

metro自己实现了很多的预设和运行时的polyfill,webpack不用再自己实现一遍,可以直接把react-native官方的拿来用即可,一个定义在根目录的rn-get-polyfills.js中,一个定义在Libraries/Core/InitializeCore.js中:

const start = Date.now();

require('./setUpGlobals');
require('./setUpPerformance');
require('./setUpSystrace');
require('./setUpErrorHandling');
require('./polyfillPromise');
require('./setUpRegeneratorRuntime');
require('./setUpTimers');
require('./setUpXHR');
require('./setUpAlert');
require('./setUpNavigator');
require('./setUpBatchedBridge');
require('./setUpSegmentFetcher');
if (__DEV__) {
  require('./checkNativeVersion');
  require('./setUpDeveloperTools');
  require('../LogBox/LogBox').install();
}

const GlobalPerformanceLogger = require('../Utilities/GlobalPerformanceLogger');
// We could just call GlobalPerformanceLogger.markPoint at the top of the file,
// but then we'd be excluding the time it took to require the logger.
// Instead, we just use Date.now and backdate the timestamp.
GlobalPerformanceLogger.markPoint(
  'initializeCore_start',
  GlobalPerformanceLogger.currentTimestamp() - (Date.now() - start),
);
GlobalPerformanceLogger.markPoint('initializeCore_end');
环境变量设置

在源码中我们可以看到是有__DEV__这个全局环境变量的,所以我们可以使用DefinePlugin进行定义。

资源处理

webpack需要对一些资源进行复制到app目录,如果是图片资源,可能还会涉及到各种分辨率的问题,例如:ldpimdpihdpi等等。复制完之后,我们还需要调用react-native提供的AssetRegistry.registerAsset方法,注册资源。

// 注册的资源需要提供如下这些属性
 __packager_asset: boolean,
 fileSystemLocation: string,
 httpServerLocation: string,
 width: ?number,
 height: ?number,
 scales: Array<number>,
 hash: string,
 name: string,
 type: string,
webpack内置方法改写

__webpack_public_path__ 这个变量默认是localhost,但是在react-native中,这个值是可以自己手动修改的,所以需要把它替换。

因为webpack是为web打包而设计的,当webpack加载脚本的时候,默认是通过document创造一个script标签加载的,webpack的源码如下:

class LoadScriptRuntimeModule extends HelperRuntimeModule {
	generate() {
		...
		const { createScript } =
			LoadScriptRuntimeModule.getCompilationHooks(compilation);

		const code = Template.asString([
			"script = document.createElement('script');",
			scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "",
			charset ? "script.charset = 'utf-8';" : "",
			`script.timeout = ${loadTimeout / 1000};`,
			`if (${RuntimeGlobals.scriptNonce}) {`,
			Template.indent(
				`script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
			),
			"}",
			uniqueName
				? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
				: "",
			`script.src = ${
				this._withCreateScriptUrl
					? `${RuntimeGlobals.createScriptUrl}(url)`
					: "url"
			};`,
			crossOriginLoading
				? Template.asString([
						"if (script.src.indexOf(window.location.origin + '/') !== 0) {",
						Template.indent(
							`script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
						),
						"}"
				  ])
				: ""
		]);

		...
	}
}

module.exports = LoadScriptRuntimeModule;

所以我们需要改写这个方法webpack.runtime.LoadScriptRuntimeModule.prototype.generate。我们需要自己定义一个加载脚本的方法,需要做几件事:

  1. __webpack_get_script_filename__使用这个方法加载文件,还需要判断是本地文件系统还是远程文件。
  2. 使用nativeModules打通原生部分,进行加载文件,java端通过catalystInstance.loadScriptFromAssets执行, ios端通过executeApplicationScript执行js代码。

renderBootstrap也需要改造,注入你自己编写的加载方法,把已经加载的模块加载进来。

热更新

要实现热更新功能,首先需要开启webpack.HotModuleReplacementPlugin,然后还要集成react-refresh进来,这个组件是为了替代以前老的webpackhot-reload用的。不过有一个插件react-refresh-webpack-plugin已经帮我们集成好了,只需拿来使用即可。

还需要处理原生端的端口映射问题,例如android可以使用adb reverse tcp实现。

而且,react-native自带的HMRClient也需要被替换,因为内置的使用了metro实现了devServer服务。

// react-native/Libraries/Utilities/HMRClient.js
const client = new MetroHMRClient(`${serverScheme}://${serverHost}/hot`);
hmrClient = client;
分包

webpack是内置支持分包功能的,我们只需要稍微做些改动即可,例如本地包和远程包区分,加载包需要对webpack的内置方法改写以及对原生部分的改造,上面已经提到过,还有sourcemap的映射,把形如index.bundle?platform=ios:567:1234这样的映射为:Hello.tsx:10:9

Android 打包优化

开启缓存

gradle3.5版本之后,推出了新的cache机制,和Android studio2.3版本引入的BuildCache不一样,这个新的cache机制会缓存每个任务的输出,而build cache只会pre-dexed的外部库。

org.gradle.caching=true
构建缓存
android.enableBuildCache = true
分配更大的内存
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
开启并行
org.gradle.parallel=true
守护进程

Gradle 是基于 JVM 的构建系统,JVM 的启动和初始化需要时间,开启 Gradle Daemon 守护进程可以节省这些时间

org.gradle.daemon = true
按需配置
org.gradle.configureondemand=true

IOS打包优化

CCache

这是目前使用得最广泛的解决方案了,就是使用缓存,不过集成比较复杂,对ios项目改造较大等。暂时不去研究。

终极优化

避免app端无意义的打包+分包+远程热更新 也就是前面我说过的,当只有js代码变动的时候,不重新打包app,从远程加载最新的代码即可。

实现思路

如何判断app端代码是否有变动?

一般的react-native项目都是有两个原生端目录的:androidios,只要这两个目录下的文件,除去打包生成的js文件(不包括入口文件)发生了变动,我们就认为需要重新打一个新的包,否则就不用重复编译。

很简单,只需要计算文件的哈希值,然后进行对比就可以了。

实战效果

我新建一个空白的项目,开始用2种不同的方式打包,使用webpack打包的时候: React-native 打包速度优化,配合webpack起飞!

而使用metro打包,需要花费33秒左右,速度慢了大概一半。这还是在我只实现了webpack打包功能的基础上,其实webapck打包速度也有很多提升的办法,比如开启缓存,并行编译,等等。

做了一丢丢优化之后:

React-native 打包速度优化,配合webpack起飞!

现在webpack打包rn的耗时为10秒左右,相比metro提升了将近1/3

再让我们来看看原生端android打包速度的提升情况,在没有优化之前打包时间为:

React-native 打包速度优化,配合webpack起飞!

花费时间大概为7分钟。

React-native 打包速度优化,配合webpack起飞!

android优化过后,打包速度大概为4分钟,减少了40%的时间。