Hot Module Replacement (HMR) for Node.js
Branch: master
Clone or download
Latest commit 1799b56 Nov 27, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode init commit Sep 27, 2017
docs/images docs Sep 27, 2017
examples use hotMod() directly call as examples Oct 1, 2017
scripts enable NPM@next CI/CD May 7, 2018
src Follow callsites v3 types Nov 27, 2018
tests Follow callsites v3 types Nov 27, 2018
.editorconfig init commit Sep 27, 2017
.gitignore use hotMod() directly call as examples Oct 1, 2017
.travis.yml wait #11 May 7, 2018
LICENSE docs Sep 27, 2017
README.md Rename @zixia to @huan Nov 27, 2018
appveyor.yml enable NPM@next CI/CD May 7, 2018
index.ts seprate log instance Oct 28, 2017
package.json 0.2.5 Nov 27, 2018
tsconfig.json fix semver typing May 7, 2018
tslint.json init commit Sep 27, 2017

README.md

HOT-IMPORT

NPM Version Downloads Powered by TypeScript node

Hot Module Replacement(HMR) for Node.js

Hot Module Reload

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.

INSTALL

$ npm install hot-import

USAGE

Talk is cheap, show me the code!

Core Code

import hotImport from 'hot-import'
const hotMod = await hotImport('./my-module')

Full Example

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

API

The only API in this module is hotImport(), it will import the module and reload it when it changes.

1. hotImport(modulePath: string): Promise<any>

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:

  1. Do const hotMod = await hotImport('./file'); Do NOT const { mod } = await hotImport('./file')
  2. Do const v = hotMod.method() to call a method inside hot module;
  3. Do console.log(hotMod.constant) to get a value inside hot module;
  4. Do const c = new hotMod.cls() to instanciate a new instance of class;

2. hotImport(modulePath: string, watch: boolean): Promise<void>

Turn the module from modulePath to be hot or cold.

  1. If watch is true, then HMR will be enabled.
  2. If watch is false, then HMR will be disabled.

3. hotImport(null, watch: boolean): Promise<void>

Turn all the modules that managed by hotImport to be hot or cold.

TEST

Build Status Windows Build status Greenkeeper badge

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

INSPIRED

This module is highly inspired by @gcaufy via his blog: 给微信机器人添加热重启功能

See Also

  1. Support hot-reload for Wechaty events listeners
  2. make js file hot-reload when use hot-require to load the file
  3. Hot Module Replacement

RELEASE NOTES

v0.1 Oct, 2017

  1. Passed all the unit tests under Windows/Linux/Mac
  2. Support TypeScript typings
  3. Initial release

AUTHOR

Huan LI <zixia@zixia.net> (http://linkedin.com/in/zixia\)

profile for zixia at Stack Overflow, Q&A for professional and enthusiast programmers

COPYRIGHT & LICENSE

  • Code & Docs © 2017 Huan LI <zixia@zixia.net>
  • Code released under the Apache-2.0 License
  • Docs released under Creative Commons