├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── README.md ├── flow └── types.js ├── index.js ├── jest └── setup.js ├── package.json ├── split-example ├── .buckconfig ├── .gitattributes ├── .gitignore ├── .splitconfig ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── BaseSubBundleActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainApplication.java │ │ │ │ ├── SampleAActivity.java │ │ │ │ ├── SampleBActivity.java │ │ │ │ └── Utils.java │ │ │ └── res │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── captures │ │ └── com.example_2017.04.18_11.47.li │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── package.json ├── run-example.sh └── src │ ├── assets │ └── naruto.jpeg │ ├── base.js │ ├── components │ ├── packagea │ │ ├── ApiOfSampleA.js │ │ └── SampleA.js │ └── packageb │ │ ├── ApiOfSampleB.js │ │ └── SampleB.js │ ├── modules │ ├── ModuleA.js │ ├── ModuleB.js │ └── index.js │ └── resolveInject.js └── src ├── __tests__ ├── assetPathUtils-test.js └── utils-test.js ├── assetPathUtils.js ├── bundler.js ├── parser.js ├── setupBabel.js └── utils.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended" 8 | ], 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | 2 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "single" 24 | ], 25 | "semi": [ 26 | "error", 27 | "always" 28 | ] 29 | } 30 | }; -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; Ignore the example subdir 3 | /example/.* 4 | 5 | ; Ignore vscode config dirs 6 | /\.vscode/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | [include] 12 | 13 | [version] 14 | ^0.43.1 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | 4 | *.idea 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Bundle Splitter 2 | 3 | Split config: [splitconfig](./split-example/.splitconfig). 4 | 5 | ## Why just an EXAMPLE (not a complete library) 6 | 7 | RN Bundle split is not just "split", it should also take two point into consideration: 8 | 9 | - **How to use them**, means native code framework change, because RN native part not support loading script in demand. 10 | - **Project structure**, you should have a clean project dependency graph (recursive dependency is bad). 11 | 12 | So, I don't want to make it to a library which be common to use. 13 | 14 | In this example, it have clear project structure and some hack to RN framework (by reflection in Java). All split-bundle related code is in **src** directory, use them with pleasure:) 15 | 16 | ## Project Structure 17 | 18 | ``` 19 | /root 20 | |- /src 21 | |- /components 22 | |- /packagea 23 | |- SampleA.js => Entry1 24 | |- ApiOfSampleA.js => Refered in SampleA.js 25 | |- /packageb 26 | |- SampleB => Entry2 27 | |- ApiOfSampleA.js => Refered in SampleA.js 28 | |- /modules 29 | |- index.js => Append to base 30 | |- ModuleA.js => Refered in index.js 31 | |- ModuleB.js => Refered in index.js 32 | |- base.js => Entry of base 33 | |- resolveInject.js => Resolve splitted resource 34 | ``` 35 | 36 | ## Usage 37 | 38 | ``` 39 | npm install 40 | node ../index.js --platform android --output build --config .splitconfig --dev false 41 | ``` 42 | See example [run-example.sh](./split-example/run-example.sh). 43 | 44 | ## Run Example 45 | 46 | ``` 47 | cd split-example 48 | npm install 49 | ./run-example.sh 50 | ``` 51 | 52 | ## License 53 | 54 | ``` 55 | Copyright 2015 Desmond Yao 56 | 57 | Licensed under the Apache License, Version 2.0 (the "License"); 58 | you may not use this file except in compliance with the License. 59 | You may obtain a copy of the License at 60 | 61 | http://www.apache.org/licenses/LICENSE-2.0 62 | 63 | Unless required by applicable law or agreed to in writing, software 64 | distributed under the License is distributed on an "AS IS" BASIS, 65 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 66 | See the License for the specific language governing permissions and 67 | limitations under the License. 68 | ``` 69 | 70 | 71 | -------------------------------------------------------------------------------- /flow/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by desmond on 4/16/17. 3 | */ 4 | 'use strict'; 5 | 6 | type BaseEntry = { 7 | index: string, 8 | includes: Array 9 | } 10 | 11 | type CustomEntry = { 12 | id: number, 13 | name: string, 14 | inject: boolean, 15 | index: string 16 | } 17 | 18 | export type Config = { 19 | root: string, 20 | dev: boolean, 21 | platform: string, 22 | packageName: string, 23 | outputDir: string, 24 | bundleDir: string, 25 | baseEntry: BaseEntry, 26 | customEntries: Array 27 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-present Desmond Yao 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Created by desmond on 4/16/17. 17 | * @flow 18 | */ 19 | 20 | 21 | 'use strict'; 22 | require('./src/setupBabel'); 23 | 24 | const fs = require('fs'); 25 | const path = require('path'); 26 | const commander = require('commander'); 27 | const Util = require('./src/utils'); 28 | const Parser = require('./src/parser'); 29 | const bundle = require('./src/bundler'); 30 | 31 | commander 32 | .description('React Native Bundle Spliter') 33 | .option('--output ', 'Path to store bundle.', 'build') 34 | .option('--config ', 'Config file for react-native-split.') 35 | .option('--platform', 'Specify bundle platform. ', 'android') 36 | .option('--dev [boolean]', 'Generate dev module.') 37 | .parse(process.argv); 38 | 39 | if (!commander.config) { 40 | throw new Error('You must enter an config file (by --config).'); 41 | } 42 | 43 | function isFileExists(fname) { 44 | try { 45 | fs.accessSync(fname, fs.F_OK); 46 | return true; 47 | } catch (e) { 48 | return false; 49 | } 50 | } 51 | 52 | const configFile = path.resolve(process.cwd(), commander.config); 53 | const outputDir = path.resolve(process.cwd(), commander.output); 54 | 55 | if (!isFileExists(configFile)) { 56 | console.log('Config file ' + configFile + ' is not exists!'); 57 | process.exit(-1); 58 | } 59 | 60 | const rawConfig = JSON.parse(fs.readFileSync(configFile, 'utf-8')); 61 | const workRoot = path.dirname(configFile); 62 | const outputRoot = path.join(outputDir, `bundle-output`); 63 | Util.ensureFolder(outputRoot); 64 | 65 | const config = { 66 | root: workRoot, 67 | dev: commander.dev === 'true', 68 | packageName : rawConfig['package'], 69 | platform : commander.platform, 70 | outputDir : path.join(outputRoot, 'split'), 71 | bundleDir : path.join(outputRoot, 'bundle'), 72 | baseEntry : { 73 | index: rawConfig.base.index, 74 | includes: rawConfig.base.includes 75 | }, 76 | customEntries : rawConfig.custom 77 | }; 78 | if (!isFileExists(config.baseEntry.index)) { 79 | console.log('Index of base does not exists!'); 80 | } 81 | 82 | console.log('Work on root: ' + config.root); 83 | console.log('Dev mode: ' + config.dev); 84 | bundle(config, (err, data) => { 85 | if (err) throw err; 86 | console.log('===[Bundle] Finish!==='); 87 | const parser = new Parser(data, config); 88 | parser.splitBundle(); 89 | }); 90 | 91 | 92 | -------------------------------------------------------------------------------- /jest/setup.js: -------------------------------------------------------------------------------- 1 | require('../src/setupBabel'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-split", 3 | "version": "1.0.0", 4 | "description": "React native bundle split tool.", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "node_modules/.bin/flow", 8 | "test": "jest" 9 | }, 10 | "author": "Desmond Yao", 11 | "license": "ISC", 12 | "jest": { 13 | "setupFiles": [ 14 | "./jest/setup.js" 15 | ] 16 | }, 17 | "dependencies": { 18 | "babel-register": "^6.24.1", 19 | "babel-traverse": "^6.24.1", 20 | "babylon": "^6.16.1", 21 | "commander": "^2.9.0", 22 | "minimatch": "^3.0.3", 23 | "uglify-js": "^2.8.22" 24 | }, 25 | "devDependencies": { 26 | "babel-jest": "^19.0.0", 27 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 28 | "babel-preset-es2015-node": "^6.1.1", 29 | "eslint": "^3.19.0", 30 | "flow-bin": "^0.43.1", 31 | "jest": "^19.0.2", 32 | "regenerator-runtime": "^0.10.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /split-example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /split-example/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /split-example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | android/app/src/main/assets/ 33 | 34 | # node.js 35 | # 36 | node_modules/ 37 | npm-debug.log 38 | yarn-error.log 39 | 40 | # BUCK 41 | buck-out/ 42 | \.buckd/ 43 | *.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/Preview.html 54 | fastlane/screenshots 55 | 56 | build 57 | -------------------------------------------------------------------------------- /split-example/.splitconfig: -------------------------------------------------------------------------------- 1 | { 2 | "package": "example", 3 | "base": { 4 | "index": "./src/base.js", 5 | "includes": [ 6 | "./src/modules/*" 7 | ] 8 | }, 9 | "custom": [{ 10 | "name": "sample_a", 11 | "index": "./src/components/packagea/SampleA.js" 12 | }, { 13 | "name": "sample_b", 14 | "index": "./src/components/packageb/SampleB.js" 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /split-example/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] 15 | lib_deps.append(':' + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.example", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.example", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /split-example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"] 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 23 87 | buildToolsVersion "23.0.1" 88 | 89 | defaultConfig { 90 | applicationId "com.example" 91 | minSdkVersion 16 92 | targetSdkVersion 22 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | } 127 | 128 | dependencies { 129 | compile fileTree(dir: "libs", include: ["*.jar"]) 130 | compile "com.android.support:appcompat-v7:23.0.1" 131 | compile "com.facebook.react:react-native:+" // From node_modules 132 | } 133 | 134 | // Run this once to be able to run the application with BUCK 135 | // puts all compile dependencies into folder libs for BUCK to use 136 | task copyDownloadableDepsToLibs(type: Copy) { 137 | from configurations.compile 138 | into 'libs' 139 | } 140 | -------------------------------------------------------------------------------- /split-example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/BaseSubBundleActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Activity; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | 8 | import com.facebook.react.ReactApplication; 9 | import com.facebook.react.ReactInstanceManager; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactRootView; 12 | import com.facebook.react.bridge.CatalystInstance; 13 | import com.facebook.react.bridge.ReactContext; 14 | import com.facebook.react.common.LifecycleState; 15 | 16 | /** 17 | * Created by desmond on 4/17/17. 18 | */ 19 | public abstract class BaseSubBundleActivity extends Activity { 20 | 21 | private ReactRootView mReactRootView; 22 | 23 | private final class LoadScriptTask extends AsyncTask { 24 | 25 | @Override 26 | protected Void doInBackground(Void... params) { 27 | CatalystInstance instance = Utils.getCatalystInstance(getReactNativeHost()); 28 | Utils.loadScriptFromAsset(BaseSubBundleActivity.this, 29 | instance, 30 | getScriptAssetPath()); 31 | return null; 32 | } 33 | 34 | @Override 35 | protected void onPostExecute(Void aVoid) { 36 | getReactNativeHost().getReactInstanceManager().attachMeasuredRootView(mReactRootView); 37 | } 38 | 39 | } 40 | 41 | private ReactNativeHost getReactNativeHost() { 42 | return ((ReactApplication) getApplication()).getReactNativeHost(); 43 | } 44 | 45 | @Override 46 | protected void onCreate(@Nullable Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | mReactRootView = new ReactRootView(this); 49 | 50 | Utils.setJsModuleName(mReactRootView, getMainComponentName()); 51 | setContentView(mReactRootView); 52 | 53 | ReactInstanceManager manager = getReactNativeHost().getReactInstanceManager(); 54 | LifecycleState state = manager.getLifecycleState(); 55 | switch (state) { 56 | case BEFORE_CREATE: 57 | case BEFORE_RESUME: 58 | default: 59 | if (!manager.hasStartedCreatingInitialContext()) { 60 | manager.createReactContextInBackground(); 61 | } 62 | manager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { 63 | @Override 64 | public void onReactContextInitialized(ReactContext reactContext) { 65 | loadScriptAsync(); 66 | } 67 | }); 68 | break; 69 | case RESUMED: 70 | loadScriptAsync(); 71 | break; 72 | } 73 | } 74 | 75 | private void loadScriptAsync() { 76 | LoadScriptTask task = new LoadScriptTask(); 77 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 78 | } 79 | 80 | @Override 81 | protected void onDestroy() { 82 | ReactInstanceManager manager = getReactNativeHost().getReactInstanceManager(); 83 | manager.detachRootView(mReactRootView); 84 | super.onDestroy(); 85 | } 86 | 87 | protected abstract String getScriptAssetPath(); 88 | 89 | protected abstract String getMainComponentName(); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.view.View; 8 | 9 | import com.facebook.react.ReactApplication; 10 | import com.facebook.react.ReactInstanceManager; 11 | import com.facebook.react.ReactNativeHost; 12 | import com.facebook.react.bridge.ReactContext; 13 | 14 | public class MainActivity extends Activity { 15 | 16 | @Override 17 | protected void onCreate(@Nullable Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | 21 | final ReactInstanceManager manager = getReactNativeHost().getReactInstanceManager(); 22 | createContext(manager); 23 | findViewById(R.id.load).setOnClickListener(new View.OnClickListener() { 24 | @Override 25 | public void onClick(View v) { 26 | createContext(manager); 27 | } 28 | }); 29 | 30 | findViewById(R.id.sample_a).setOnClickListener(new View.OnClickListener() { 31 | @Override 32 | public void onClick(View v) { 33 | startActivity(new Intent(v.getContext(), SampleAActivity.class)); 34 | } 35 | }); 36 | 37 | findViewById(R.id.sample_b).setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | startActivity(new Intent(v.getContext(), SampleBActivity.class)); 41 | } 42 | }); 43 | } 44 | 45 | private void createContext(final ReactInstanceManager manager) { 46 | Utils.recreateReactContextInBackgroundInner(manager); 47 | manager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { 48 | @Override 49 | public void onReactContextInitialized(ReactContext context) { 50 | Utils.moveResume(manager, true); 51 | } 52 | }); 53 | } 54 | 55 | private ReactNativeHost getReactNativeHost() { 56 | return ((ReactApplication) getApplication()).getReactNativeHost(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.common.logging.FLog; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactNativeHost; 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.shell.MainReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | import javax.annotation.Nullable; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | public boolean getUseDeveloperSupport() { 22 | return false; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | return Arrays.asList( 28 | new MainReactPackage() 29 | ); 30 | } 31 | 32 | @Nullable 33 | @Override 34 | protected String getBundleAssetName() { 35 | return "bundle/base/index.bundle"; 36 | } 37 | }; 38 | 39 | @Override 40 | public ReactNativeHost getReactNativeHost() { 41 | return mReactNativeHost; 42 | } 43 | 44 | @Override 45 | public void onCreate() { 46 | super.onCreate(); 47 | FLog.setMinimumLoggingLevel(FLog.VERBOSE); 48 | SoLoader.init(this, /* native exopackage */ false); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/SampleAActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | public class SampleAActivity extends BaseSubBundleActivity { 4 | 5 | @Override 6 | protected String getScriptAssetPath() { 7 | return "bundle/sample_a/index.bundle"; 8 | } 9 | 10 | @Override 11 | protected String getMainComponentName() { 12 | return "SampleA"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/SampleBActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | public class SampleBActivity extends BaseSubBundleActivity { 4 | 5 | @Override 6 | protected String getScriptAssetPath() { 7 | return "bundle/sample_b/index.bundle"; 8 | } 9 | 10 | @Override 11 | protected String getMainComponentName() { 12 | return "SampleB"; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /split-example/android/app/src/main/java/com/example/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.support.annotation.Nullable; 6 | import android.support.annotation.WorkerThread; 7 | 8 | import com.facebook.react.ReactInstanceManager; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactRootView; 11 | import com.facebook.react.bridge.CatalystInstance; 12 | import com.facebook.react.bridge.ReactContext; 13 | import com.facebook.react.cxxbridge.CatalystInstanceImpl; 14 | 15 | import java.lang.reflect.Field; 16 | import java.lang.reflect.InvocationTargetException; 17 | import java.lang.reflect.Method; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * Utils for sample, just some hacks since I can't change source code in this project. 23 | * 24 | * Created by desmond on 4/17/17. 25 | */ 26 | class Utils { 27 | 28 | private static Set sLoadedScript = new HashSet<>(); 29 | 30 | static void recreateReactContextInBackgroundInner(ReactInstanceManager manager) { 31 | try { 32 | Method method = ReactInstanceManager.class.getDeclaredMethod("recreateReactContextInBackgroundInner"); 33 | method.setAccessible(true); 34 | method.invoke(manager); 35 | } catch (NoSuchMethodException e) { 36 | e.printStackTrace(); 37 | } catch (IllegalAccessException e) { 38 | e.printStackTrace(); 39 | } catch (InvocationTargetException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | static void moveResume(ReactInstanceManager manager, boolean force) { 45 | try { 46 | Method method = ReactInstanceManager.class.getDeclaredMethod("moveToResumedLifecycleState", boolean.class); 47 | method.setAccessible(true); 48 | method.invoke(manager, force); 49 | } catch (NoSuchMethodException e) { 50 | e.printStackTrace(); 51 | } catch (IllegalAccessException e) { 52 | e.printStackTrace(); 53 | } catch (InvocationTargetException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | static void setJsModuleName(ReactRootView rootView, String moduleName) { 59 | try { 60 | Field field = ReactRootView.class.getDeclaredField("mJSModuleName"); 61 | field.setAccessible(true); 62 | field.set(rootView, moduleName); 63 | } catch (NoSuchFieldException e) { 64 | e.printStackTrace(); 65 | } catch (IllegalAccessException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | @Nullable 71 | static CatalystInstance getCatalystInstance(ReactNativeHost host) { 72 | ReactInstanceManager manager = host.getReactInstanceManager(); 73 | if (manager == null) { 74 | return null; 75 | } 76 | 77 | ReactContext context = manager.getCurrentReactContext(); 78 | if (context == null) { 79 | return null; 80 | } 81 | return context.getCatalystInstance(); 82 | } 83 | 84 | @Nullable 85 | public static String getSourceUrl(CatalystInstance instance) { 86 | try { 87 | Field field = CatalystInstanceImpl.class.getDeclaredField("mSourceURL"); 88 | field.setAccessible(true); 89 | return (String) field.get(instance); 90 | } catch (NoSuchFieldException e) { 91 | e.printStackTrace(); 92 | } catch (IllegalAccessException e) { 93 | e.printStackTrace(); 94 | } 95 | return null; 96 | } 97 | 98 | @WorkerThread 99 | static void loadScriptFromAsset(Context context, 100 | CatalystInstance instance, 101 | String assetName) { 102 | if (sLoadedScript.contains(assetName)) { 103 | return; 104 | } 105 | try { 106 | String source = "assets://" + assetName; 107 | Method method = CatalystInstanceImpl.class.getDeclaredMethod("loadScriptFromAssets", 108 | AssetManager.class, 109 | String.class); 110 | method.setAccessible(true); 111 | method.invoke(instance, context.getAssets(), source); 112 | sLoadedScript.add(assetName); 113 | } catch (IllegalAccessException e) { 114 | e.printStackTrace(); 115 | } catch (NoSuchMethodException e) { 116 | e.printStackTrace(); 117 | } catch (InvocationTargetException e) { 118 | e.printStackTrace(); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /split-example/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |