看到微博上 vue-loader
开始支持代码热替换的消息
真让人坐不住, 赶忙翻代码看下, 结果看不懂
现实的压力仍是在的, react-hot-loader
已经不推荐使用了
做者搞了 React Transform, 并且针对 Babel 优化, 整套新的东西
而简聊用 actions-recorder
加 react-hot-loader
多少有些风险
让 actions-recorder
更好调试是颇有必要的css
然而问题的重点是, Webpack 是跟很强大但文档挺难懂的项目
周末我大体翻了一些文档, 晚上作了写尝试, 大体达成了基本的功能
https://github.com/webpack/docs/wiki/hot-module-replacement
http://jlongster.com/Backend-Apps-with-Webpack--Part-III
实际上一个 API 就能解决的事情, 太简单了.. 只是门槛真高啊html
细节强烈建议读上边两篇文档, 到底仍是花了很长篇幅解释的
简单的彷佛, 大体上就是 Webpack 当中模块其实是 module tree
这个和 DOM tree 很相似, update 在其中就是一个个事件
update 事件会冒泡, 能够经过代码捕捉和中止冒泡
若是冒泡到根节点, 那么说明代码没有处理 update 事件, 因而刷新页面vue
module.hot.accept
react
Wiki 上的这个服务端例子, 我以为最能说清 Webpack 是怎么作的
不过这个例子怎么运行我就彻底不知道了... 文档没写清楚
注意其中的 module.hot
和 module.hot.accept
两个 API
前者只是状态, 无视, 然后者就是用来捕捉 update
事件进行处理的所有webpack
var requestHandler = require("./handler.js"); var server = require("http").createServer(); server.on("request", requestHandler); server.listen(8080); // check if HMR is enabled if (module.hot) { // accept update of dependency module.hot.accept("./handler.js", function() { // replace request handler of server server.removeListener("request", requestHandler); requestHandler = require("./handler.js"); server.on("request", requestHandler); }); }
能够看到代码里指明了 ./handle.js
模块的更新须要被接受
而后, update 发生时, 原有的旧代码 requestHandler
先移除掉
而后从新调用 require
加载新的代码, 而后再从新绑定一次
内部的操做, Webpack 自动完成的, 我按照这个写法也基本没遇到问题
总之这样就完成了模块热替换的实现git
module.hot.dispose
github
这个 API 说实话我还不会用, 如今只知道大概的意思
单单上边的方案只适合处理纯函数的代码, 也就是没有内部状态
若是一个模块, 好比说会往 DOM 上挂载节点的,
那么模块移除的时候它就应该自动把挂上去的节点带走销毁.dispose()
方法大体上就是处理这样一个场景用的, 看代码web
// addStyleTag(css: string) => HTMLStyleElement var addStyleTag = require("./addStyleTag"); var element = addStyleTag(".rule { attr: name }"); module.exports = null; // check if HMR is enabled if(module.hot) { // accept itself module.hot.accept(); // removeStyleTag(element: HTMLStyleElement) => void var removeStyleTag = require("./removeStyleTag"); // dispose handler module.hot.dispose(function() { // revoke the side effect removeStyleTag(element); }); }
固然, 实际尝试处理 dispose
我估计还会遇到各类坑, 不像折腾了
还好这个 API 尤雨溪大神也不用, 我就先不要趟浑水了
还好 React 里的东西主要都是一些纯函数的编码风格主导的, 私有状态很少app
此外在 module.hot
还绑定了一些能访问内部状态和操做的 API
具体状况比较复杂, 我如今彻底不知道如何下手
若是颇有兴趣或者很无聊, 能够继续看看两个著名的 loader 怎么搞的:
https://github.com/webpack/style-loader/blob/master/index.js
https://github.com/gaearon/react-hot-loader/blob/master/index.jsless
上边的代码会有个不足, 就是 module.hot
相关的代码直接在源码里了
其实不属于应用的代码, 只是专门处理模块热替换的, 有点难看
前面这些 loader 考虑到了这一点, 因而会自动生成代码进去
也就是好比一个 React 组件的 module.exports
是个组件,
那么这个组件前面后面就爆包裹一段生成的代码, 用来调用 accept
方法
确实是个颇有效果的方法
不用这样的办法, 我在 Amok 的文档上见到一种
由于 Amok 是经过 Remote Debugging API 动态替换 JavaScript 的
就是说任何状态很大程度上可以保留的, 只是更新函数代码
因此它能够在代码替换完成后, 直接顶层调用一次 render
搞定
http://amokjs.com/getting_started.html
window.addEventListener('patch', function(event) { React.render(app); });
我在现有的 actions-recorder
方案中模拟了一下
隐隐会有一些问题, 就是组件内部状态我未必能确保
这个更多取决于 React 内部机制怎样处理 render
过程具体的更新
反过来看 react-hot-loader
的方案会是更有保障的
就是每一个组件在更新时类方法都单独更新了一遍, 同时手动维护好 state
只是代码量真心不小, 也没有讲解, 我并不知道具体细节如何
actions-recorder
中为了支持热替换, 我增长了一个方法hotSetup
, 和 setup
相似传入对象配置 schema
和 updater
代码我觉直接复制文件了, 应该能看懂... 也就是代码多复制了一遍嘛:
React = require 'react' recorder = require 'actions-recorder' ReactDOM = require 'react-dom' Immutable = require 'immutable' require('volubile-ui/ui/index.less') # 正常引用 schema 和 updater schema = require './schema' updater = require './updater' Page = React.createFactory require './app/page' # 第一次初始化 recorder.setup initial: schema.store updater: updater # 第一次生成 render 函数, 以及作绑定 render = (store, core) -> ReactDOM.render Page({store, core}), document.querySelector('.demo') recorder.request render recorder.subscribe render # 开始处理模块热替换, 特征代码, 很容易认出来吧 if module.hot # 声明两个入口文件 module.hot.accept ['./updater', './schema'], -> # 从新调用新的模块 schema = require './schema' updater = require './updater' # 此次用 hotSetup 传入参数, hotSetup 会强制建立新的 store recorder.hotSetup initial: schema.store updater: updater # 组件也要处理一下, 跟上边逻辑有点区别 module.hot.accept ['./app/page'], -> # 从新调用新的组件 Page = React.createFactory require './app/page' # 先解绑旧的 render 函数, 同时生成新的 render 函数 # ... 考虑到变量是能够修改的, 下面引用的 Page 是新的也许不用写那么多 recorder.unsubscribe render render = (store, core) -> ReactDOM.render Page({store, core}), document.querySelector('.demo') # 新的 render 函数先调用一次更新下界面, 而后从新监听 recorder.request render recorder.subscribe render
这样的代码, 实现的效果就是, updater schema Page
代码能够热替换updater
代码的更新在热替换后能够立刻从回调的 store 中体现schema
更新会更新 actions-recorder
初始状态, 也是更新 storePage
更新会引发整个 DOM tree 从新渲染, 就是 DOM 的局部更新
初步的 Demo 已经运行正常, 复杂场景还须要多加试验
而后 actions
可能涉及到很多的代码, 我发现也能够试着改一下
首先 todo.coffee
文件里写把 actions
须要的方法写好
而后用这个文件... 其实就是写两遍啦, 都不须要解释了
因为 JavaScript 数据可变, 后面的 exports
就都能访问到新的代码了
todo = require './todo' exports.create = todo.create exports.update = todo.update exports.toggle = todo.toggle exports.archive = todo.archive if module.hot module.hot.accept ['./todo'], -> todo = require './todo' exports.create = todo.create exports.update = todo.update exports.toggle = todo.toggle exports.archive = todo.archive
那么用视频演示下:
http://www.tudou.com/programs/view/3zCKPo6a1ZU/?phd=99
大体就这样吧, 后面的开发当中我再想法子深刻一下
哪位有资源但愿也能共享, Webpack 这个文档看着太累了
目前代码热替换比较惊人的, 除了 Webpack 还有 Amok 和 Elm, Figwheel
Amok 是用 Chrome DevTools 内部 API, 对现成的大项目项目支持度存疑
后边两个是函数式社区超前并且高大上的玩意, 我只能看看视频了解下
另外 Redux 做者搞的那一整套东西, 我也就望而生畏一下了
首先热替换带来的界面开发的效率提升是有目共睹的, 回想下改 CSS
另外一方面, 函数式的理念让代码热替换变得可行甚至实用
我想这也会极大的改变你们理解程序, 理解图形应用的方式
对于程序的抽象, 能够远远超出二进制指令用 subroutine 整合的抽象
而是程序执行的 tree 能够分为函数, 状态, 还有 IO 几个变和不变的部分,
某一时刻代码修改了, 这棵 tree 其实能够快速更新为等效的新的 tree
就是 React 更新 DOM 同样, 某些场景理解对了, 更新就很是巧妙
开发效率不是件小事, 后续事情还不少, 我文章先结尾了
照例加个公司招聘邮箱 <hr@teambition.com>