diff --git a/packages/metro-bundler/src/DeltaBundler/index.js b/packages/metro-bundler/src/DeltaBundler/index.js index 54c6df22..8aaaef83 100644 --- a/packages/metro-bundler/src/DeltaBundler/index.js +++ b/packages/metro-bundler/src/DeltaBundler/index.js @@ -12,6 +12,7 @@ 'use strict'; +const DeltaPatcher = require('./DeltaPatcher'); const DeltaTransformer = require('./DeltaTransformer'); import type Bundler from '../Bundler'; @@ -42,6 +43,7 @@ class DeltaBundler { _bundler: Bundler; _options: MainOptions; _deltaTransformers: Map = new Map(); + _deltaPatchers: Map = new Map(); _currentId: number = 0; constructor(bundler: Bundler, options: MainOptions) { @@ -81,6 +83,20 @@ class DeltaBundler { id: bundleId, }; } + + async buildFullBundle(options: Options): Promise { + const deltaBundle = await this.build(options); + + let deltaPatcher = this._deltaPatchers.get(deltaBundle.id); + + if (!deltaPatcher) { + deltaPatcher = new DeltaPatcher(); + + this._deltaPatchers.set(deltaBundle.id, deltaPatcher); + } + + return deltaPatcher.applyDelta(deltaBundle).stringify(); + } } module.exports = DeltaBundler; diff --git a/packages/metro-bundler/src/Server/index.js b/packages/metro-bundler/src/Server/index.js index e6448711..6d2c1cd1 100644 --- a/packages/metro-bundler/src/Server/index.js +++ b/packages/metro-bundler/src/Server/index.js @@ -14,8 +14,10 @@ const AssetServer = require('../AssetServer'); const Bundler = require('../Bundler'); +const DeltaBundler = require('../DeltaBundler'); const MultipartResponse = require('./MultipartResponse'); +const crypto = require('crypto'); const debug = require('debug')('Metro:Server'); const defaults = require('../defaults'); const emptyFunction = require('fbjs/lib/emptyFunction'); @@ -93,6 +95,7 @@ export type Options = {| +sourceExts: ?Array, +transformCache: TransformCache, transformModulePath?: string, + useDeltaBundler: boolean, watch?: boolean, workerPath: ?string, |}; @@ -182,6 +185,7 @@ class Server { _symbolicateInWorker: Symbolicate; _platforms: Set; _nextBundleBuildID: number; + _deltaBundler: DeltaBundler; constructor(options: Options) { const reporter = @@ -218,6 +222,7 @@ class Server { transformCache: options.transformCache, transformModulePath: options.transformModulePath || defaults.transformModulePath, + useDeltaBundler: options.useDeltaBundler, watch: options.watch || false, workerPath: options.workerPath, }; @@ -285,6 +290,13 @@ class Server { this._symbolicateInWorker = symbolicate.createWorker(); this._nextBundleBuildID = 1; + + if (this._opts.useDeltaBundler) { + this._deltaBundler = new DeltaBundler(this._bundler, { + getPolyfills: this._opts.getPolyfills, + polyfillModuleNames: this._opts.polyfillModuleNames, + }); + } } end(): mixed { @@ -735,7 +747,7 @@ class Server { return this._reportBundlePromise(buildID, options, bundleFromScratch()); } - processRequest( + async processRequest( req: IncomingMessage, res: ServerResponse, next?: () => mixed, @@ -774,6 +786,11 @@ class Server { return; } + if (this._opts.useDeltaBundler && requestType === 'bundle') { + await this._processRequestUsingDeltaBundler(req, res); + return; + } + const options = this._getOptionsFromUrl(req.url); const requestingBundleLogEntry = log( createActionStartEntry({ @@ -859,6 +876,67 @@ class Server { }); } + async _processRequestUsingDeltaBundler( + req: IncomingMessage, + res: ServerResponse, + ) { + const options = this._getOptionsFromUrl(req.url); + const requestingBundleLogEntry = log( + createActionStartEntry({ + action_name: 'Requesting bundle', + bundle_url: req.url, + entry_point: options.entryFile, + }), + ); + + const buildID = this.getNewBuildID(); + + if (!this._opts.silent) { + options.onProgress = (transformedFileCount, totalFileCount) => { + this._reporter.update({ + buildID, + type: 'bundle_transform_progressed', + transformedFileCount, + totalFileCount, + }); + }; + } + + this._reporter.update({ + buildID, + bundleOptions: options, + type: 'bundle_build_started', + }); + + const bundle = await this._deltaBundler.buildFullBundle({ + ...options, + deltaBundleId: this.optionsHash(options), + }); + + const etag = crypto.createHash('md5').update(bundle).digest('hex'); + + if (req.headers['if-none-match'] === etag) { + debug('Responding with 304'); + res.writeHead(304); + res.end(); + + return; + } + + res.setHeader('Content-Type', 'application/javascript'); + res.setHeader('ETag', etag); + res.setHeader('Content-Length', String(Buffer.byteLength(bundle))); + res.end(bundle); + + this._reporter.update({ + buildID, + type: 'bundle_build_done', + }); + + debug('Finished response'); + log(createActionEndEntry(requestingBundleLogEntry)); + } + _symbolicate(req: IncomingMessage, res: ServerResponse) { const symbolicatingLogEntry = log(createActionStartEntry('Symbolicating')); diff --git a/packages/metro-bundler/src/index.js b/packages/metro-bundler/src/index.js index 16fb03e1..1e9c16ad 100644 --- a/packages/metro-bundler/src/index.js +++ b/packages/metro-bundler/src/index.js @@ -154,6 +154,7 @@ function toServerOptions(options: Options): ServerOptions { sourceExts: options.sourceExts, transformCache: options.transformCache || TransformCaching.useTempDir(), transformModulePath: options.transformModulePath, + useDeltaBundler: options.useDeltaBundler, watch: typeof options.watch === 'boolean' ? options.watch : !!options.nonPersistent, workerPath: options.workerPath, };