Hot Module Replacement(HMR) for Node.js
Hot Module Replacement (HMR) is a feature to inject updated modules into the active runtime. It's like LiveReload for every module.
HMR exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways:
- Retain application state which is lost during a full reload.
- Save valuable development time by only updating what's changed.
-- WebPack Concepts - https://webpack.js.org/concepts/hot-module-replacement/
hot-import
is a NPM module that enable you to do HMR with just one line of code.
$ npm install hot-import
Talk is cheap, show me the code!
import hotImport from 'hot-import'
const hotMod = await hotImport('./my-module')
import * as assert from 'assert'
import * as fs from 'fs'
import * as path from 'path'
import hotImport from 'hot-import'
async function main() {
const MODULE_CODE_42 = 'module.exports = () => 42'
const MODULE_CODE_17 = 'module.exports.default = () => 17'
const MODULE_FILE = path.join(__dirname, 't.js')
fs.writeFileSync(MODULE_FILE, MODULE_CODE_42)
const hotMod = await hotImport(MODULE_FILE)
const fourtyTwo = hotMod()
console.log('fourtyTwo =', fourtyTwo) // Output: fourtyTwo = 42
assert(fourtyTwo === 42, 'first get 42')
fs.writeFileSync(MODULE_FILE, MODULE_CODE_17)
await new Promise(setImmediate) // wait io event loop finish
const sevenTeen = hotMod()
console.log('sevenTeen =', sevenTeen) // Output sevenTeen = 17
assert(sevenTeen === 17, 'get 17 after file update & hot reloaded')
await hotImport(MODULE_FILE, false) // stop hot watch
}
main()
.catch(console.error)
Output:
42
17
The above code is in the example/
directory. Npm script demo
will run it for you:
git clone git@github.com:zixia/hot-import.git
cd hot-import
npm install
npm run demo
The only API in this module is hotImport()
, it will import the module and reload it when it changes.
Import a module from modulePath
as a Hot Module.
// load './mod' as a hot module
const hotMod = await hotImport('./mod')
// ... do staffs like the following five ways
// const c = hotMod() // 1. default export is a Function
// const c = new hotMod() // 2. default export is a Class
// const c = hotMod.func() // 3. export is a { Function }
// const c = new hotMod.cls() // 4. export is a { Class }
// const c = hotMod.constant // 5. export is a { const }
// make module cold, not to watch/reload anymore.
await hotImport('./mod', false)
Attention:
- Do
const hotMod = await hotImport('./file')
; Do NOTconst { mod } = await hotImport('./file')
- Do
const v = hotMod.method()
to call a method inside hot module; - Do
console.log(hotMod.constant)
to get a value inside hot module; - Do
const c = new hotMod.cls()
to instanciate a new instance of class;
Turn the module from modulePath
to be hot or cold.
- If
watch
istrue
, then HMR will be enabled. - If
watch
isfalse
, then HMR will be disabled.
Turn all the modules that managed by hotImport
to be hot or cold.
This module is fully tested under Linux/Mac/Windows.
$ npm test
> hot-import@0.0.24 test /home/zixia/git/hot-import
> npm run lint && npm run test:unit
> hot-import@0.0.24 lint /home/zixia/git/hot-import
> npm run check-node-version && tslint --version && tslint --project tsconfig.json "{src,tests}/**/*.ts" --exclude "tests/fixtures/**" --exclude "dist/" && npm run clean && tsc --noEmit
> hot-import@0.0.24 check-node-version /home/zixia/git/hot-import
> check-node-version --node ">= 7"
node: 8.5.0
npm: 5.3.0
yarn: not installed
5.7.0
> hot-import@0.0.24 clean /home/zixia/git/hot-import
> shx rm -fr dist/*
> hot-import@0.0.24 test:unit /home/zixia/git/hot-import
> blue-tape -r ts-node/register -r source-map-support/register "src/**/*.spec.ts" "tests/**/*.spec.ts"
TAP version 13
# callerResolve()
# relative file path
ok 1 should turn relative to absolute
# absolute file path
ok 2 should keep absolute as it is
# newCall()
ok 3 should instanciate class with constructor arguments
# hotImport()
# class module(export=)
ok 4 should get expected values from instance of class in module
ok 5 should import module class with right id:1
ok 6 should get same module file for fixtures(change file content only)
ok 7 should get expected values from instance of class in module
ok 8 should import module class with right id:2
# variable module(export const answer=)
ok 9 should get expected values from variable in module
ok 10 should get same module file for fixtures(change file content only)
ok 11 should get expected values from variable in module
# importFile()
# const value
ok 12 should import file right with returned value original
# class
ok 13 should instanciated class with constructor argument
ok 14 should import module class with right id
# refreshImport()
ok 15 should be refreshed to new value
# purgeRequireCache()
ok 16 should get returnValue from module
ok 17 should keep value in require cache
ok 18 should get returnValue again after purge
ok 19 should no KEY exists any more
# cloneProperties()
# object
ok 20 should clone the text property
# class
ok 21 should clone the prototype for class
# hotImport
ok 22 should get 42 for meaning of life
# callerResolve
ok 23 should resolve based on the consumer file path
# 1/4. fs.writeFileSync then fs.writeFile
ok 24 should monitored file change event at least once
ok 25 should monitored file change event at most twice
ok 26 should instanciated a watcher
# 2/4. fs.writeFileSync then fs.writeFileSync
ok 27 should instanciated a watcher
# 3/4. fs.writeFile then fs.writeFile
ok 28 should monitored 1 change event
ok 29 should monitored 0 rename event
ok 30 should instanciated a watcher
# 4/4. fs.writeFile then fs.writeFileSync
ok 31 should instanciated a watcher
# fixtures
ok 32 should monitored file change event at least once
ok 33 should monitored file change event at most twice
ok 34 should not monitored file rename event
ok 35 should instanciated a watcher
1..35
# tests 35
# pass 35
# ok
This module is highly inspired by @gcaufy via his blog: 给微信机器人添加热重启功能
- Support hot-reload for Wechaty events listeners
- make js file hot-reload when use hot-require to load the file
- Hot Module Replacement
- Passed all the unit tests under Windows/Linux/Mac
- Support TypeScript typings
- Initial release
Huan LI <zixia@zixia.net> (http://linkedin.com/in/zixia)
- Code & Docs © 2017 Huan LI <zixia@zixia.net>
- Code released under the Apache-2.0 License
- Docs released under Creative Commons