Play with webpack hot replacement feature
Before that we focus at webpack hot module replacement, let’s see some reasons why I choosed React/Webpack couple to create my blog and some extra websites:
- build must be fast
- development must be fast
- learning stack must be fast
- rendering must be fast
- SEO has to be good :) ..and :
With AngularJS, I have to admit learning curve was not that easy and even Meteor solution is sticked to lot of things behind.
I learn React step by step (sometimes 2 steps), component lyfecycle, virtual dom concepts and all that kind of stuff and it is just the beginning.
React facilitates isomorphism which is mainly a good approach for SEO when coding essentially in Javascript.
Then a very common task developer dislikes, as it is very time consuming, consist in handling deployment, build etc… Grunt, Gulp help us, Browserify is very nice but Webpack is a monster.
I wrote this article after having played with webpack HMR module and its API.
If you do not know webpack, do not worry, you still have time to play with it, and you should :)
Live coding in React
You may all know @gaearon who put a big stone with react-hot-loader
Purpose of this webpack’s loader ( we will see later what is a ‘loader’) is to reload (re inject) in live your react components with your live code updates. To resume, you code with your favorite editor and webpack rebundle everything for you in live and re render page with updates.
Entry code is here, I will give my explanations about it then:
What is a webpack loader
As described in documentation, loader are transformations that can be applied on a resource file. A specific loader is executed during webpack compilation process if current compiled file extension (ex: .jpeg, .js, …) matches loader’s configuration.
By instance, imagine an image file and a loader generating thumbnails from it in different sizes.
A loader is mainly specified by :
- an extension(s) file to match: .png, .js, .whatever
- a name
Availables loaders
Write a loader
Why ? because you may have specific needs, webpack is your swiss knife.
I give an example later, let’s come back to React Hot loader first.
How React Hot Loader works
To resume, react hot webpack loader enhances all of your js code files with some webpack HMR “Hot Module Replacement” javascript code stuffs.
Before loader has passed:
MyComponent code
After
Some HMR code
MyComponent code
Some HMR code
But what happens in this “Some HMR code” ?
-
it checks current resource (.js jsx file) is a React component and if not do nothing when HMR triggers an update event. - it watches some HMR update events and when necessary reloads your react component by doing tricky stuff with current react component to be updated.
How HMR api works
Concepts are explained here and example and api here
Until now I have just played with the following methods:
// Accept code updates for the specified dependencies.
// The callback is called when dependencies were replaced.
accept(dependencies: string[], callback: (updatedDependencies))
// Accept code updates for this module without notification of parents.
// This should only be used if the module doesn’t export anything.
accept([errHandler])
dispose(callback: (data: object))
// called when update is triggered, data object may contain your logic, cache...
// see webpack website for explanations :)
To play with HMR you need to use webpack HotModuleReplacementPlugin plugin, example of use can be found here
We will take a small example.
main.js
var dep = require('./dep');
var dep2 = require('./dep2');
dep.doSomething("darul"); // "do darul"
dep2.doSomething("darul"); // "do DARUL"
dep.js
module.exports = {
doSomething: function(name) {
console.log('do ' + name);
}
}
dep2.js
module.exports = {
doSomething: function(name) {
console.log('do ' + name.toUpperCase());
}
}
If you want your code to be aware of some HMR events you need to add some custom webpack code into you own code.
// check if HMR is enabled
if(module.hot) {
// do your stuff
}
Then we update code from dep.js
dep.js
module.exports = {
doSomething: function(name) {
// console.log('do ' + name);
console.log('do something ' + name);
}
}
If you do nothing, it will still output ‘do darul’ to console, OMG.
What you need is to enhance your module to accept or decline changes:
main.js
var dep = require('./dep');
var dep2 = require('./dep2');
dep.doSomething("darul"); // fn call
dep2.doSomething("darul"); // fn call
// check if HMR is enabled
if(module.hot) {
// example for putting something from current component
// module.hot.data = {
// key: value
// };
// accept update of dependency
module.hot.accept(["./dep.js", "./dep2.js"], function() {
// most of the time just reaffect require instance
// with new fresh dependency module
dep = require('./dep');
dep2 = require('./dep2');
// and maybe recall fn call again
dep.doSomething();
dep2.doSomething();
}
}
My idea
My idea was to create a loader which will enhance all my js files with the following HMR code:
// check if HMR is enabled
if(module.hot) {
// accept update of dependency
//
module.hot.accept([ALL_DEPENDENCIES_CALLED_IN_THIS_MODULE], function() {
// most of the time just reaffect require instance
// with new fresh dependencies
// iterate over all dependencies and require it again.
dep = require('./dep');
dep2 = require('./dep2');
// and maybe recall fn call from these modules.
dep.doSomething(params);
dep.doSomethingAgain(params);
dep2.doSomething();
}
}
My loader code is here:
How it works ?
1> using regexp to match all require instructions like
var dep = require('something');
will extract ‘name’ of variable and ‘path’ of dependency which gives 2 strings “dep”/”something”.
2> using regexp to match function call for this required module, example
var dep = require('something');
dep.doSomething(params);
dep.doSomethingAgain(params);
will extract which gives 2 strings “doSomething(params)”/”doSomethingAgain(params)”.
After loader has finished working, your webpack code looks something like
if (true) {
module.hot.accept([3, 4], function() {
dep = __webpack_require__(3);
dep2 = __webpack_require__(4);
dep.doSomething(params);
dep.doSomethingAgain(params);
dep2.doSomething();
});
}
It is not perfect and all common nodejs modules or external library should be escaped…but that was a try.
My react swiss kit it here web-react
Tags: Javascript NodeJS React Webpack