Reagieren Sie auf native Unterpakete und das Laden von Unterpaketen auf der iOS-Seite

Präambel

Das Unternehmen möchte eine plattformübergreifende Lösung entwickeln.Da die meisten mobilen Seiten zuvor von H5 geschrieben wurden, verfügt das Unternehmen über eine große Anzahl von Mitarbeitern für den Stack der JavaScript-Technologie, und die H5-Hot-Update-Lösung wurde vor dem Projekt verwendet, und das will es auch um Hot Update weiterhin zu verwenden, um Online-Probleme zu lösen, also am Ende entschieden, React Native zu verwenden.


RN Ende Unterverpackung (Auspacken)

Wie wir alle wissen, lädt die native iOS-Seite den React Native-Code hauptsächlich durch Laden des konvertierten jsBundle. Einige Projekte werden von Anfang an mit reinem RN entwickelt und müssen nur ein index.ios.jsbundle laden. Unser Projekt ist jedoch, React Native-Module auf Basis von Hybrid hinzuzufügen, und auch ein Hot-Update ist erforderlich.Um die Größe jedes Hot-Update-Pakets so gering wie möglich zu halten, ist es notwendig, das Laden von Unterpaketen zu verwenden.

Was ich hier verwende, ist die Metro-basierte Zuliefermethode, die auch die Hauptmethode auf dem Markt ist. createModuleIdFactory(path)Diese Entpackungsmethode befasst sich hauptsächlich mit den Methoden und Methoden, die von den Metro-Tools während der Phase "Serialisierung" aufgerufen werden  processModuleFilter(module). createModuleIdFactory(path)ist der absolute Pfad des übergebenen Moduls pathund gibt einen eindeutigen Pfad für dieses Modul zurück Id. processModuleFilter(module)Dann kann das Modul gefiltert werden, sodass der Inhalt des Geschäftsmoduls nicht in das gemeinsame Modul geschrieben wird. Als nächstes werden die spezifischen Schritte und Codes erläutert.

1. Erstellen Sie zunächst eine common.js-Datei, die alle öffentlichen Module importiert

require('react')
require('react-native')
...

2. Metro verwendet diese common.js als Einstiegsdatei, um ein gemeinsames Bundle-Paket zu erstellen und gleichzeitig die moduleId aller öffentlichen Module aufzuzeichnen , aber es gibt ein Problem: Jedes Mal, wenn Sie das Metro-Paket starten, wird die moduleId automatisch von 0 inkrementiert. Dies führt zur Duplizierung verschiedener JSBundle-IDs . Um eine Duplizierung von IDs zu vermeiden, besteht die derzeitige Mainstream-Praxis in der Branche darin, den Pfad des Moduls als moduleId zu verwenden (da der Pfad des Moduls grundsätzlich lautet behoben und kollidiert nicht), wodurch das Problem von ID-Konflikten gelöst wird. Metro stellt die createModuleIdFactory-Funktion bereit, wir können die ursprüngliche Auto-Increment-Logik in dieser Funktion überschreiben und die moduleId des öffentlichen Moduls in die txt-Datei schreiben

I) Befehle in package.json konfigurieren:

"build:common:ios": "rimraf moduleIds_ios.txt && react-native bundle --entry-file common.js --platform ios --config metro.common.config.ios.js --dev false --assets-dest ./bundles/ios --bundle-output ./bundles/ios/common.ios.jsbundle",

II) Die metro.common.config.ios.js-Datei

const fs = require('fs');
const pathSep = require('path').sep;


function createModuleId(path) {
  const projectRootPath = __dirname;
  let moduleId = path.substr(projectRootPath.length + 1);

  let regExp = pathSep == '\\' ? new RegExp('\\\\', "gm") : new RegExp(pathSep, "gm");
  moduleId = moduleId.replace(regExp, '__');
  return moduleId;
}

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
  serializer: {
    createModuleIdFactory: function () {
      return function (path) {
        const moduleId = createModuleId(path)

        fs.appendFileSync('./moduleIds_ios.txt', `${moduleId}\n`);
        return moduleId;
      };
    },
  },
};

III) Generierte Datei moduleIds_ios.txt

common.js
node_modules__react__index.js
node_modules__react__cjs__react.production.min.js
node_modules__object-assign__index.js
node_modules__react-native__index.js
node_modules__react-native__Libraries__Components__AccessibilityInfo__AccessibilityInfo.ios.js
......

3. Beginnen Sie nach dem Packen der öffentlichen Module mit dem Packen der Geschäftsmodule. Der Schlüssel zu diesem Schritt besteht darin, die moduleId des öffentlichen Moduls zu filtern (die ID des öffentlichen Moduls wurde im vorherigen Schritt in moduleIds_ios.txt aufgezeichnet. Metro stellt die Methode processModuleFilter bereit, die zum Filtern von Modulen verwendet werden kann. Die Verarbeitung dieses Teils wird hauptsächlich in die Datei metro.business.config.ios.js geschrieben, in welche Datei geschrieben werden soll, hängt hauptsächlich von der Datei ab, die im obersten Befehl package.json angegeben ist.

const fs = require('fs');
const pathSep = require('path').sep;

const moduleIds = fs.readFileSync('./moduleIds_ios.txt', 'utf8').toString().split('\n');

function createModuleId(path) {
  const projectRootPath = __dirname;
  let moduleId = path.substr(projectRootPath.length + 1);

  let regExp = pathSep == '\\' ? new RegExp('\\\\', "gm") : new RegExp(pathSep, "gm");
  moduleId = moduleId.replace(regExp, '__');
  return moduleId;
}

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
  serializer: {
    createModuleIdFactory: function () {
      return createModuleId;
    },
    processModuleFilter: function (modules) {
      const mouduleId = createModuleId(modules.path);

      if (modules.path == '__prelude__') {
        return false
      }
      if (mouduleId == 'node_modules__metro-runtime__src__polyfills__require.js') {
        return false
      }

      if (moduleIds.indexOf(mouduleId) < 0) {
        return true;
      }
      return false;
    },
    getPolyfills: function() {
      return []
    }
  },
  resolver: {
    sourceExts: ['jsx', 'js', 'ts', 'tsx', 'cjs', 'mjs'],
  },
};

Zusammenfassend ist festzustellen, dass die Unterauftragsarbeiten auf der Seite von React Native im Wesentlichen abgeschlossen sind.


Laden von Unterpaketen auf der iOS-Seite

1. Die iOS-Seite muss zuerst das öffentliche Paket laden

-(void) prepareReactNativeCommon{
  NSDictionary *launchOptions = [[NSDictionary alloc] init];
  self.bridge = [[RCTBridge alloc] initWithDelegate:self
                                      launchOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{   
    return  [NSURL URLWithString:[[self getDocument] stringByAppendingPathComponent:@"bundles/ios/common.ios.jsbundle"]];
}

2. Nach dem Laden des öffentlichen Pakets müssen Sie das Geschäftspaket laden. Um das Geschäftspaket zu laden, müssen Sie die Methode executeSourceCode verwenden, aber dies ist eine private Methode von RCTBridge, und Sie müssen nur eine Klassifizierung von RCTBridge erstellen die .h-Datei reicht aus.Durch den Laufzeitmechanismus wird die interne Implementierung der Methode executeSourceCode ausgeführt.

#import <Foundation/Foundation.h>


@interface RCTBridge (ALCategory) // 暴露RCTBridge的私有接口

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync;

@end
-(RCTBridge *) loadBusinessBridgeWithBundleName:(NSString*) fileName{
    
    NSString * busJsCodeLocation = [[self getDocument] stringByAppendingPathComponent:fileName];
    NSError *error = nil;

    NSData * sourceData = [NSData dataWithContentsOfFile:busJsCodeLocation options:NSDataReadingMappedIfSafe error:&error];
    NSLog(@"%@", error);

    [self.bridge.batchedBridge  executeSourceCode:sourceData sync:NO];
    
    return self.bridge;
}

- (NSString *)getDocument {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"app"];
    return path;
}

Der Code der vollständigen ALAsyncLoadManager-Klasse, die öffentliche Pakete und Business-Pakete lädt:

#import "ALAsyncLoadManager.h"
#import "RCTBridge.h"
#import <React/RCTBridge+Private.h>

static ALAsyncLoadManager *instance;

@implementation ALAsyncLoadManager

+ (ALAsyncLoadManager *) getInstance{
  @synchronized(self) {
    if (!instance) {
      instance = [[self alloc] init];
    }
  }
  return instance;
}

-(void) prepareReactNativeCommon{
  NSDictionary *launchOptions = [[NSDictionary alloc] init];
  self.bridge = [[RCTBridge alloc] initWithDelegate:self
                                      launchOptions:launchOptions];
}

-(RCTBridge *) loadBusinessBridgeWithBundleName:(NSString*) fileName{
    
    NSString * busJsCodeLocation = [[self getDocument] stringByAppendingPathComponent:fileName];
    NSError *error = nil;

    NSData * sourceData = [NSData dataWithContentsOfFile:busJsCodeLocation options:NSDataReadingMappedIfSafe error:&error];
    NSLog(@"%@", error);

    [self.bridge.batchedBridge  executeSourceCode:sourceData sync:NO];
    
    return self.bridge;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{   
    return  [NSURL URLWithString:[[self getDocument] stringByAppendingPathComponent:@"bundles/ios/common.ios.jsbundle"]];
}

- (NSString *)getDocument {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"app"];
    return path;
}

3. Es gibt noch einen weiteren wichtigen Punkt: Da das öffentliche Paket und das Business-Paket separat geladen werden, muss das Business-Paket nach dem Laden des öffentlichen Pakets geladen werden, und die Geschwindigkeit sollte so schnell wie möglich sein, sagen einige Leute im Internet dass, wenn die App gestartet wird, sie mit dem öffentlichen Paket der Initialisierungsbrücke geladen wird, denke ich, dass dies eine sehr faule Art der Verarbeitung ist, die viel Leistung verbraucht.Was ich hier verwende, ist, die Benachrichtigung zu überwachen, die das öffentliche Paket hat Sobald die Benachrichtigung eingegangen ist, beginnt das Business Package zu laden.

@interface ALRNContainerController ()
@property (strong, nonatomic) RCTRootView *rctContainerView;
@end

@implementation ALRNContainerController

- (void)viewDidLoad {

[[NSNotificationCenter defaultCenter] addObserver:self
                                      selector:@selector(loadRctView)                                                                
                                      name:RCTJavaScriptDidLoadNotification         
                                      object:nil];
}

- (void)loadRctView {
    [self.view addSubview:self.rctContainerView];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (RCTRootView *)rctContainerView {
            
            RCTBridge* bridge = [[MMAsyncLoadManager getInstance] buildBridgeWithDiffBundleName:[NSString stringWithFormat:@"bundles/ios/%@.ios.jsbundle",_moduleName]];
            
            _rctContainerView = [[RCTRootView alloc] initWithBridge:bridge moduleName:_moduleName initialProperties:@{
                @"user" : @"用户信息",
                @"params" : @"参数信息"
            }];
        
        _rctContainerView.frame = UIScreen.mainScreen.bounds;
        return _rctContainerView;
}

- (void)dealloc
{
    //清理bridge,减少性能消耗
    [ALAsyncLoadManager getInstance].bridge = nil;
    [self.rctContainerView removeFromSuperview];
    self.rctContainerView = nil;
}

Zusammenfassend sind die Ladearbeiten der Unterpakete auf der iOS-Seite grob abgeschlossen.


Das Obige ist der gesamte Prozess des Ladens von Unterpaketen auf der React Native-Seite und der nativen Seite, und dann gibt es noch die Implementierung von Hot-Updates und die Implementierung der im Projekt verwendeten Komponentenbibliothek. Wenn Sie es nützlich finden, geben Sie ihm einen Stern !

Guess you like

Origin blog.csdn.net/weixin_42433480/article/details/129913092