在開始本文前,先簡單說下咱們在開發RN項目中,本地的node服務究竟扮演的是什麼樣的角色。在咱們的RN APP中有配置本地開發的地方,只要咱們輸入咱們本地的IP和端口號8081就能夠開始調試本地代碼,其實質是APP發起了一個請求bundle文件的HTTP請求,而咱們的node server在接到request後,開始對本地項目文件進行babel,pack,最後返回一個bundle.js。而本地的node服務扮演的角色還不止如此,好比啓動基礎服務dev tool,HMR等node
HMR(Hot Module Replacement)模塊熱替換,能夠類比成Webpack的Hot Reload。可讓你在代碼變更後不用reload app,代碼直接生效,且當前路由棧不會發生改變react
先貼上我的整理的的一個HMR熱更新的過程
咱們來逐步按流程對應相應的源碼分析web
# react-native/local-cli/server/runServer.js const serverInstance = http.createServer(app).listen( args.port, args.host, () => { attachHMRServer({ httpServer: serverInstance, path: '/hot', packagerServer, }); wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy'); ms = messageSocket.attachToServer(serverInstance, '/message'); webSocketProxy.attachToServer(serverInstance, '/devtools'); readyCallback(); } );
本地啓動在8081啓動HTTP服務的同時,也初始化了本地HMR的服務,這裏在初始化的時候注入了packagerServer,爲的是能訂閱packagerServer提供的watchman回調,同時也爲了能拿到packagerServer提供的getDependencies方法,這樣能在HMR內部拿到文件的依賴關係(相互require的關係)segmentfault
#react-native/local-cli/server/util/attachHMRServer.js // 略微簡化下代碼 function attachHMRServer({httpServer, path, packagerServer}) { ... const WebSocketServer = require('ws').Server; const wss = new WebSocketServer({ server: httpServer, path: path, }); wss.on('connection', ws => { ... getDependencies(params.platform, params.bundleEntry) .then((arg) => { client = { ... }; packagerServer.setHMRFileChangeListener((filename, stat) => { ... client.ws.send(JSON.stringify({type: 'update-start'})); stat.then(() => { return packagerServer.getShallowDependencies({ entryFile: filename, platform: client.platform, dev: true, hot: true, }) .then(deps => { if (!client) { return []; } const oldDependencies = client.shallowDependencies[filename]; // 分析當前文件的require關係是否與以前一致,若是require關係有變更,須要從新對文件的dependence進行分析 if (arrayEquals(deps, oldDependencies)) { return packagerServer.getDependencies({ platform: client.platform, dev: true, hot: true, entryFile: filename, recursive: true, }).then(response => { const module = packagerServer.getModuleForPath(filename); return response.copy({dependencies: [module]}); }); } return getDependencies(client.platform, client.bundleEntry) .then(({ dependenciesCache: depsCache, dependenciesModulesCache: depsModulesCache, shallowDependencies: shallowDeps, inverseDependenciesCache: inverseDepsCache, resolutionResponse, }) => { if (!client) { return {}; } return packagerServer.buildBundleForHMR({ entryFile: client.bundleEntry, platform: client.platform, resolutionResponse, }, packagerHost, httpServerAddress.port); }) .then(bundle => { if (!client || !bundle || bundle.isEmpty()) { return; } return JSON.stringify({ type: 'update', body: { modules: bundle.getModulesIdsAndCode(), inverseDependencies: client.inverseDependenciesCache, sourceURLs: bundle.getSourceURLs(), sourceMappingURLs: bundle.getSourceMappingURLs(), }, }); }) .then(update => { client.ws.send(update); }); } ).then(() => { client.ws.send(JSON.stringify({type: 'update-done'})); }); }); client.ws.on('close', () => disconnect()); }) }
RN最舒服的地方就是命名規範,基本看到函數名就能知道他的職能,咱們來看上面這段代碼,attachHMRServer這個總共作了如下幾件事:react-native
咱們已經看到了socket的發送方,那麼一定存在一個接收方,也就是這裏要講的HMRClient,首先先來看這邊註冊函數緩存
#react-native/Libraries/BatchedBridge/BatchedBridge.js const MessageQueue = require('MessageQueue'); const BatchedBridge = new MessageQueue( () => global.__fbBatchedBridgeConfig, serializeNativeParams ); const Systrace = require('Systrace'); const JSTimersExecution = require('JSTimersExecution'); BatchedBridge.registerCallableModule('Systrace', Systrace); BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution); BatchedBridge.registerCallableModule('HeapCapture', require('HeapCapture')); if (__DEV__) { BatchedBridge.registerCallableModule('HMRClient', require('HMRClient')); }
這邊就是HMRClient註冊階段,貼這段代碼實際上是由於發現RN裏的JS->Native,Native->JS通訊是經過MQ(MessageQueue)實現的,而追溯到最裏層發現居然是一套setTimeout,setImmediate的異步隊列...扯遠了,有空的話,能夠專門分享一下。babel
#react-native/Libraries/Utilities/HMRClient.js activeWS.onmessage = ({ data }) => { ... modules.forEach(({ id, code }, i) => { ... const injectFunction = typeof global.nativeInjectHMRUpdate === 'function' ? global.nativeInjectHMRUpdate : eval; code = [ '__accept(', `${id},`, 'function(global,require,module,exports){', `${code}`, '\n},', `${JSON.stringify(inverseDependencies)}`, ');', ].join(''); injectFunction(code, sourceURLs[i]); }); } };
HMRClient作的事就很簡單了,接到socket傳入的String,直接eval運行,這邊的code形以下圖
咱們能夠看到這邊是一個__accept函數在接受這個變動後的HMR bundleapp
#react-native/packager/react-packager/src/Resolver/polyfills/require.js const accept = function(id, factory, inverseDependencies) { //在當前模塊映射表裏查找,若是找的到將其Code進行替換,並執行,若沒有,從新進行聲明 const mod = modules[id]; if (!mod) { //從新申明 define(id, factory); return true; // new modules don't need to be accepted } const {hot} = mod; if (!hot) { console.warn( 'Cannot accept module because Hot Module Replacement ' + 'API was not installed.' ); return false; } // replace and initialize factory if (factory) { mod.factory = factory; } mod.hasError = false; mod.isInitialized = false; //真正進行熱替換的地方 require(id); //當前模塊熱更新後須要執行的回調,通常用來解決循環引用 if (hot.acceptCallback) { hot.acceptCallback(); return true; } else { // need to have inverseDependencies to bubble up accept if (!inverseDependencies) { throw new Error('Undefined `inverseDependencies`'); } //將當前moduleId的逆向依賴傳入,熱更新他的逆向依賴,遞歸執行 return acceptAll(inverseDependencies[id], inverseDependencies); } }; global.__accept = accept;
這邊的代碼就不進行刪減了,accept函數接受三個參數,moduleId,factory,inverseDependencies。異步
簡單來講accept作的事情就是判斷變更當前模塊是新加的須要define,仍是說直接更新內存裏已存在的module,同時沿着他的逆向依賴樹,所有load一遍,一直到最頂級的AppResigterElement,這樣熱替換的過程就完成了,形以下圖socket
那麼問題就來了,react的View展示對state是強依賴的,從新load一遍,state不會丟失麼,實際上在load的過程當中,RN把老的ref傳入了,因此繼承了以前的state
講到這還略過了最重要的一點,爲何說我這邊熱替換了內存中module,並執行了一遍,個人App就能拿到這個更新後的代碼,咱們依舊拿代碼來講
#react-native/packager/react-packager/src/Resolver/polyfills/require.js global.require = require; global.__d = define; const modules = Object.create(null); function define(moduleId, factory) { if (moduleId in modules) { // prevent repeated calls to `global.nativeRequire` to overwrite modules // that are already loaded return; } modules[moduleId] = { factory, hasError: false, isInitialized: false, exports: undefined, }; if (__DEV__) { // HMR modules[moduleId].hot = createHotReloadingObject(); // DEBUGGABLE MODULES NAMES // avoid unnecessary parameter in prod const verboseName = modules[moduleId].verboseName = arguments[2]; verboseNamesToModuleIds[verboseName] = moduleId; } } function require(moduleId) { const module = __DEV__ ? modules[moduleId] || modules[verboseNamesToModuleIds[moduleId]] : modules[moduleId]; return module && module.isInitialized ? module.exports : guardedLoadModule(moduleId, module); }
RN複寫了require,這樣全部模塊其實拿到的是這裏