question - do you know what a bundler is? Please raise your hand! > (Quite good) Nice! I can see a lot of hands! Please bear with me as we get through those slides > (Not good) Great! This might be the most educational presentation I have ever made then haha
dependencies from your code. There was no `import` or `require` statements. So how did we make functions of our code visible to the outer world and how did we import functions from other people’s code?
actually neat. All set with just a few lines of code, right? Imagine this scenario: Your prototype works out, your application grows and you end up looking into splitting your ﬁle into smaller, reusable pieces.
on slide. Now, the code that will get executed in the script tag should have access to all globals deﬁned in the preceding ﬁles. The problem here is the order. What if foo.js wanted to use bar.js code. Manipulating the order ﬁles are loaded and executed is a design ﬂaw. I call it script hell. From callback hell we know it’s bad!
In Node.js environment, when you want to load a ﬁle, it’s physically accessible on the disc, in most cases next to existing ﬁle. In our context, however, we need to make an HTTP request to load a ﬁle that has never been loaded by the browser. And that request is asynchronous.
We can bundle all the ﬁles into one, so that we have all required dependencies in memory at the time of `require` function being called. And that’s the primary reason for bundling explained in 5 minutes.
be perceived as a natural evolution of task runners, like Gulp or Grunt that happened over the course of past few years. There are lots of responsibilities in scope for them rather than just getting your code into one chunk for sake of organising dependencies.
to split your code into various bundles which can then be loaded on demand or in parallel. In ES2015 compatible environment for example, you can use `import` function to obtain an instance of a module. The `Promise` is resolved as soon as the loading process is ﬁnished.
2 ships with a built-in support for tree shaking. That means it can determine what parts of your modules are not used (e.g. module foo is never required) and eﬀectively remove those parts. Combined with regular miniﬁers, like Uglify, it contributes to smaller bundle size that gets delivered to users.
constraints we just talked in context of web, applicable here as well. Just to give you a great example: On iOS, React Native uses built-in Safari to execute your application logic. Its features and performance changes on per system basis. You will ﬁnd a diﬀerent set of ES6 supported on your testing Mac than on iOS8, 9 or 10. So you deﬁnitely want to transpile your code to lowest common subset supported.
pretty similar to web, right? We have access to some of the same polyﬁlls, globals, APIs. The language and spec is also the same. We bundle code the same way. There are even modules on npm that work in both environments. However, there’s one thing that is fundamentally diﬀerent.
every day. At ﬁrst glance, all it does is it serves your entire application as a single bundle that can be accessed on your localhost. Pretty simple, right? Then, why don’t we spawn Webpack and conﬁgure it to serve the bundle at same URL? I like experiments, so I thought - why not try doing it.
where I could just point to an entry point and deﬁne some other settings for React Native to load the bundle. Then, I thought of all React Native speciﬁc features I knew I have to support for the ﬁrst implementation to work.
run, I used `resolve.extensions` to deﬁne order ﬁles should be resolved. In this case, I wanted my implementation to load ﬁrst, platform speciﬁc ﬁle, then native ﬁle and end up with loading just a js ﬁle.
Facebook proprietary `node-haste` system. It’s pretty simple - any ﬁle annotated with @providesModule, can be globally required within the project by its name. That way, you can have ﬁles spread across entire repository and require them w/o knowing their exact location. Of course, Webpack has no idea how to support this and what it will try is to look inside node_modules.
map where keys are names of modules that can be required globally and values - absolute path to a ﬁle that should get loaded. My solution turned out to be quite dumb, but perfect in terms of performance. I decided to glob for `js` ﬁles in provided array of folders and just parse doc blocks of every ﬁle that had it.
got it running. In less than 7 seconds Webpack created and served a bundle on port and URL I wanted. Great, I thought - nothing but declarative Webpack conﬁg and I rule the world. My excitement went away as soon as I opened a native client. I have noticed lots of features were missing. Example: “Live reload” wasn’t working at all.
context of React Native, there has to be more than just a bundler that is running. Turns out, running `react-native start` spins up entire set of tools focused on providing a better developer experience. At its core, there’s a bundler. All the rest are just complementary features built on top of it.
to develop, debug and package an application. It builds on top of an arbitrary bundler, like Webpack and ships with pre-conﬁgured set of transforms required for the code to run in particular environment.
going and see if I can make Webpack work with React Native. The plan was to support all its features, including symlinks, that never worked with default packager yet. I was in particular interested where’s the boundary, that is - how many features I’ll have to write on top in order to make it a drop-in replacement.
was the Chrome Debugger. On standard React Native app, turning that mode opens a new tab in the browser. However, there wasn’t any API on iOS or Android that I was aware of to open an arbitrary app on a Mac. I quickly realised that it has to be packager that opens a Chrome tab, not React Native. Since it is a process running on a host machine, it has access to all required APIs. Now how do those two pieces talk to each other?
having diﬀerent responsibility. Together, they form a tool that provides the developer experience we know from React Native. It is a really cool and powerful concept - being able to plug & play certain parts as a middleware makes it really hackable and customisable. As you can see, the last middleware here in the list is the `metro-bundler`, the one that serves your bundle by default.
communication is established by React Native, calling Packager endpoint, e.g. launch-devtools. Then, the handler is executing procedure on a host machine and reports back to React Native client. It is very similar to a remote procedure call, in distributed computing, with a diﬀerence that everything happens on your localhost!
instead of spawning webpack from command line and relying purely on webpack conﬁg, I decided to use express server and webpack-dev-middleware. That way, I could still get an instance of express server and get the ability to extend it with the features standard packager has by default.
associated with a particular request. As soon as change to ﬁles happen (in case of webpack, it can be done by listening to `done` event), all connections are closed with `changed: true` indicating that live reload is required on the client.
Chrome, React Native executes a remote procedure to launch dev tools inside Chrome tab when necessary. Right after, both device and debugger connect with each other by using Websocket connection. Since there’s no way for them to connect directly, they do it through Packager, which proxies all messages. If for some reason the connection cannot be established, we end up with “Runtime not ready for debugging” message we already saw on past slides.
started looking into understanding why error messages didn’t have proper stack traces. Now I didn’t include this in my initial prototype - Steve Kellock from Inﬁnite Red contributed it later - thank you! However, I ﬁnd it very interesting so decided to describe it as if it was there from the very beginning.
an error - React Native asks packager to symbolicate the error stack trace. It does that by passing an entire callstack and expects the same structure, but with appropriate lines, columns and ﬁles to be returned as a response.
instance of `SourceMapConsumer`. We pass it a raw source map (as for example read from ﬁle system). SourceMapConsumer represents a parsed source map which we can query for information about the original ﬁle positions by giving it a ﬁle position in the generated source.
frames received from the device) to the original source, line and column information we got by querying SourceMapConsumer. Such array is then sent back to React Native which quickly replaces unsymbolicated stack trace with a better one.
this is I believe the bundler implementation should be pluggable. The packager in React Native does way more things than just compiling our code. And those things are not trivial - some of them were carefully designed over the course of months. From my experience, it took me two weeks to implement Webpack to work with React Native. If the entire platform was pluggable, it would’ve been a breeze.
for long one of the most common issues with React Native development, especially for library developers. React Native promised the workﬂow we know from Web and Node.js in the world of mobile development. Breaking that expectation was painful for many. The default Metro Bundler uses Watchman to listen to ﬁle changes and reacts accordingly. Now, Watchman is a Facebook open source library constrained to provide maximum performance at their scale. One of its limitations is that it doesn’t listen for changes in symlinked locations and it’s unclear whether this feature is going to land anytime soon - not a priority.
irrelevant to us matter a lot. It’s great that we get performance improvements for free. However, at the same time, I believe one should be able to opt-in for a slightly slower but more feature-rich solution.
is a drop-in replacement for existing packager. It builds on top of open tools, like Webpack. At its core, there’s Express server with pluggable middlewares. It’s a hackable dev server with predictable workﬂow. Most of the code snippets you have seen so far in this talk can be found in the repo.
Two of them were already mentioned today, so to recap: (ﬁrst) First one was to have a support for symlinks, something we could die for (second) Second was to embrace potential of plug-ability and make something, that can be extended in an easy way
just awesome! What I like about it is its extensive, well documented interface with a broad ecosystem. It opens lots of exciting possibilities, like using new extensions for your ﬁles, using CSS, new loaders or even Typescript. Also, if you are already using Webpack on web, you could share the conﬁg and maximise the percentage of common business logic in your codebase.
are working on. Currently, our top priority is to increase its adoption so that we get more bug reports and can move forward! If the idea of Haul sounds interesting to you and you are likely to use it in the future, please give it a go and let us know your feedback!
looking into making Haul a platform for other bundlers to run. There are faster alternatives to Webpack, like Fusebox. I believe that one should be able to use whatever works for him to bundle the app and still, get ﬁrst class developer experience that Haul gives him.