Slide 1

Slide 1 text

Building JavaScript bundles for React Native Rafael Oleza @rafeca

Slide 2

Slide 2 text

What is Metro?

Slide 3

Slide 3 text

Why Metro • Fast • Scalable • Integrated

Slide 4

Slide 4 text

Anatomy of a JS bundle runtime module 1 ... module n startup code

Slide 5

Slide 5 text

Anatomy of a JS bundle module 1 ... module n startup code runtime • Register modules • Require modules

Slide 6

Slide 6 text

Anatomy of a JS bundle global.__d = function(factory, id) { modules[id] = { factory, loaded: false, module: {exports: {}}, }; } module 1 ... module n startup code runtime

Slide 7

Slide 7 text

Anatomy of a JS bundle global.__r = function(id) { if (!modules[id]) { throw new Error(`Module ${id} not found`); } const {module, factory, loaded} = modules[id]; if (loaded) { return module.exports; } modules[id].loaded = true; factory(global, global.__r, module, module.exports); return module.exports; } module 1 ... module n startup code runtime

Slide 8

Slide 8 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __d(function(global, require, module, exports) { 'use strict'; const foo = require(22); module.exports = foo; }, 1673);

Slide 9

Slide 9 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __d(function(global, require, module, exports) { 'use strict'; const foo = require(22); module.exports = foo; }, 1673);

Slide 10

Slide 10 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __d(function(global, require, module, exports) { 'use strict'; const foo = require(22); module.exports = foo; }, 1673);

Slide 11

Slide 11 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __d(function(global, require, module, exports) { 'use strict'; const foo = require(22); module.exports = foo; }, 1673);

Slide 12

Slide 12 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __d(function(global, require, module, exports) { 'use strict'; const foo = require(22); module.exports = foo; }, 1673);

Slide 13

Slide 13 text

Anatomy of a JS bundle module 1 ... module n startup code runtime __r(0);

Slide 14

Slide 14 text

Anatomy of a JS bundle __r(0); (function(global) { const modules = Object.create(null); global.__d = function(factory, id) { modules[id] = { factory, module: {exports: {}}, loaded: false, }; } global.__r = function(id) { if (!modules[id]) { throw new Error(`Module ${id} not found`); } const {module, factory, loaded} = modules[id]; if (loaded) { return module.exports; } modules[id].loaded = true; factory(global, global.__r, module, module.exports); return module.exports; } })(this); __d(function(global, require, module, exports) { const foo = require(1); module.exports = foo; }, 0); __d(function(global, require, module, exports) { module.exports = 'Hello, world'; }, 1);

Slide 15

Slide 15 text

__d(function(global, require, module, exports) { const foo = require(1); module.exports = foo; }, 0); __d(function(global, require, module, exports) { module.exports = 'Hello, world'; }, 1); (function(global) { const modules = Object.create(null); global.__d = function(factory, id) { modules[id] = { factory, module: {exports: {}}, loaded: false, }; } global.__r = function(id) { if (!modules[id]) { throw new Error(`Module ${id} not found`); } const {module, factory, loaded} = modules[id]; if (loaded) { return module.exports; } modules[id].loaded = true; factory(global, global.__r, module, module.exports); return module.exports; } })(this); Anatomy of a JS bundle __r(0);

Slide 16

Slide 16 text

The bundling process

Slide 17

Slide 17 text

The bundling process 2 phases: • Build the Dependency Graph • Graph Serialization

Slide 18

Slide 18 text

The bundling process 2 phases: • Build the Dependency Graph • Graph Serialization

Slide 19

Slide 19 text

• Transform modules • Collect dependencies • Resolve dependencies Dependency Graph

Slide 20

Slide 20 text

Dependency Graph Input: ./index.js import foo from './foo'; import leftPad from 'left-pad'; // ...

Slide 21

Slide 21 text

Dependency Graph Input: Output: ./index.js import foo from './foo'; import leftPad from 'left-pad'; // ... const foo = require('./foo'); const leftPad = require('left-pad'); // ...

Slide 22

Slide 22 text

Dependency Graph Input: Output: ./index.js import foo from './foo'; import leftPad from 'left-pad'; // ... const foo = require(1); const leftPad = require(2); // ... [ "./foo", "left-pad", ]

Slide 23

Slide 23 text

Dependency Graph Input: Output: ./index.js import foo from './foo'; import leftPad from 'left-pad'; // ... __d(function(global, require) { const foo = require(1); const bar = require(2); // ... }); [ "./foo", "left-pad", ]

Slide 24

Slide 24 text

Dependency Graph ./foo.js ./index.js Input: import foo from './foo'; import leftPad from 'left-pad'; // ... Output: [ "./foo", "left-pad", ] __d(function(global, require) { const foo = require(1); const bar = require(2); // ... });

Slide 25

Slide 25 text

Dependency Graph ./node_modules/left-pad/index.js ./foo.js ./index.js Input: Output: [ "./foo", "left-pad", ] __d(function(global, require) { const foo = require(1); const bar = require(2); // ... }); import foo from './foo'; import leftPad from 'left-pad'; // ...

Slide 26

Slide 26 text

Dependency Graph ./node_modules/left-pad/index.js ./foo.js ./index.js ./bar.js ./baz.js

Slide 27

Slide 27 text

Dependency Graph ./node_modules/left-pad/index.js ./foo.js ./index.js ./bar.js ./baz.js

Slide 28

Slide 28 text

Dependency Graph Transforming files is slow • Parse JS into AST • Mutate AST • Generate JS & SourceMaps from AST

Slide 29

Slide 29 text

1) Caching system • Extensible • Layered architecture class MyCacheStore { async get(key) { // Return value } async set(key, value) { // Set value } async clear() { // Clear all cached values } }

Slide 30

Slide 30 text

1) Caching system • Fulfilled by a CI job • Makes initial builds considerably faster HTTP Remote cache

Slide 31

Slide 31 text

2) Parallelization jest-worker main.js import Worker from 'jest-worker'; async function main() { const worker = new Worker('./worker.js'); const result = await worker.hello('Alice'); } worker.js export function hello(param) { return 'Hello, ' + param; }

Slide 32

Slide 32 text

3) Delta Bundler • Available in dev mode • Avoids regenerating the Dependency Graph • Sends only changes to the devices • Enabled by default in Android (iOS soon!)

Slide 33

Slide 33 text

3) Delta Bundler ./bar.js ./foo.js ./index.js ./baz.js A ./index.js A ./foo.js A ./bar.js A ./baz.js

Slide 34

Slide 34 text

3) Delta Bundler ./bar.js ./foo.js ./index.js ./baz.js M ./foo.js

Slide 35

Slide 35 text

3) Delta Bundler ./bar.js ./foo.js ./index.js ./baz.js A ./new.js M ./bar.js ./new.js

Slide 36

Slide 36 text

3) Delta Bundler ./bar.js ./foo.js ./index.js ./baz.js M ./foo.js ./new.js

Slide 37

Slide 37 text

3) Delta Bundler ./bar.js ./foo.js ./index.js ./baz.js M ./foo.js M ./bar.js D ./new.js ./new.js

Slide 38

Slide 38 text

Scalable by design 1) Caching system 2) Parallelization 3) Delta Bundler

Slide 39

Slide 39 text

The bundling process 2 phases: • Build the Dependency Graph • Graph Serialization

Slide 40

Slide 40 text

Graph Serialization • Takes the graph object as an input • Generates bundle from it

Slide 41

Slide 41 text

runtime module 1 ... module n startup code Plain JS bundle

Slide 42

Slide 42 text

Random Access Modules bundle MOD_0_LENGTH MOD_0_OFFSET MOD_1_LENGTH MOD_1_OFFSET … 0xFB0BD1E5 NUM_MODULES MOD_0 \0 MOD_1 \0 START \0 … START_LENGTH 00 32 64 Header Table of contents List of modules

Slide 43

Slide 43 text

Random Access Modules bundle runtime module 1 ... module n startup code JS VM RN standard bundle

Slide 44

Slide 44 text

JS VM RN Random Access Modules bundle runtime startup code RAM bundle

Slide 45

Slide 45 text

Random Access Modules global.__r = function(id) { if (!modules[id]) { throw new Error(`Module ${id} not found`); } // ... }

Slide 46

Slide 46 text

Random Access Modules global.__r = function(id) { if (!modules[id]) { nativeRequire(id); } // ... } MOD_0_LEN MOD_0_OFF MOD_1_LEN MOD_1_OFF … 0xFB0BD1E5 NUM_MODULES MOD_0 \0 MOD_1 \0 START \0 … START_LEN 00 32 64

Slide 47

Slide 47 text

Random Access Modules More info: https://tinyurl.com/rambundles Reduced startup times Less memory consumption

Slide 48

Slide 48 text

Inline requires const foo = require('./foo'); const leftPad = require('left-pad'); export default function sayHi() { const message = `Hi ${foo.getName()}`; return leftPad(message, 4); }

Slide 49

Slide 49 text

Inline requires export default function sayHi() { const message = `Hi ${require('./foo').getName()}`; return require('left-pad')(message, 4); }

Slide 50

Slide 50 text

Inline requires const debugModule = require('./debug'); export default function sayHi() { if (__DEV__) { return debugModule(); } return 'Hi'; }

Slide 51

Slide 51 text

Inline requires const debugModule = require('./debug'); export default function sayHi() { return 'Hi'; }

Slide 52

Slide 52 text

Inline requires export default function sayHi() { if (__DEV__) { return require('./debug')(); } return 'Hi'; }

Slide 53

Slide 53 text

Inline requires export default function sayHi() { return 'Hi'; }

Slide 54

Slide 54 text

Takeaways Slow build times? Use remote cache https://tinyurl.com/metrocaches Slow app startup? Use RAM bundles https://tinyurl.com/enablerambundles

Slide 55

Slide 55 text

Thanks! https://github.com/facebook/metro