開發的時候,爲了更好的體驗,咱們每每會但願修改完代碼,按ctrl+s保存代碼後,瀏覽器就呈現出咱們最新的修改結果。利用目前的一些工具,咱們能夠實現這樣的效果,本文主要講解客戶端代碼修改後熱重載
、服務端代碼修改後重啓node進程
和node進程重啓後自動刷新瀏覽器
這三部分的具體實現過程。html
咱們利用webpack來實現客戶端熱重載的功能。node
如下依賴後續會使用到:react
nom install react-hot-loader webpack-hot-middleware @types/react-hot-loader @types/webpack-hot-middleware @types/webpack-env
PS: @types/webpack-env主要用於給webpack裏諸如module,require這些變量進行類型補充定義,而不是默認使用node端的定義
webpack
和dev中間件同樣,webpack-hot-middleware中間件也須要通過改造以用於koa:git
// ./src/webpack/koa-webpack-hot-middleware.ts import * as Koa from 'koa'; import * as webpack from 'webpack'; import * as webpackHotMiddleware from 'webpack-hot-middleware'; import * as stream from 'stream'; export default (compiler: webpack.Compiler, opts?: webpackHotMiddleware.Options) => { const hotMiddleware = webpackHotMiddleware(compiler, opts); return (ctx: Koa.Context, next: () => Promise<any>): any => { const streamer = new stream.PassThrough(); ctx.body = streamer; const res: any = {}; res.write = streamer.write.bind(streamer); res.writeHead = (state: number, headers?: any) => { ctx.state = state; ctx.set(headers); }; return hotMiddleware(ctx.req, res, next); }; };
這裏主要是修改了res的兩個方法,使得原hot中間件調用res的這兩個方式時,能夠做用到ctx對象的相關屬性上去。github
如今來應用它:web
./src/webpack/webpack-dev-server.ts ... import koaWebpackHotMiddleware from './koa-webpack-hot-middleware'; ... export default (app: Koa, serverCompilerDone) => { ... app.use(koaWebpackDevMiddleware(clientCompiler, devMiddlewareOptions)); app.use(koaWebpackHotMiddleware(clientCompiler)); ... }; ...
須要在dev中間件後使用。express
按照官方介紹,使用webpack-hot-middleware的話,咱們須要在webpack配置上作一些改動:npm
./src/webpack/client.ts ... ((clientDevConfig.entry as any).client as string[]).unshift( 'webpack-hot-middleware/client', ); // 熱重載配置 ((clientDevConfig.entry as any).vendor as string[]).unshift( 'react-hot-loader/patch', ); // 熱重載配置 ... const tsRule = getTsRule('./src/webpack/tsconfig.client.json'); (tsRule.use as object[]).unshift({ loader: 'react-hot-loader/webpack', }); ... (clientDevConfig.module as webpack.NewModule).rules.push( ... tsRule, ... ); ... clientDevConfig.plugins.push( ... new webpack.HotModuleReplacementPlugin(), // 熱重載配置 ... ); ...
主要涉及入口文件,規則,插件這三部份內容。json
根據react-hot-loader官方介紹,咱們須要給根節點包裹其提供的AppContainer組件,介於同構,因此先後端都須要加。
這個是服務端的:
./src/server/bundle.tsx ... import { AppContainer } from 'react-hot-loader'; ... export default { ... render() { ... const html = renderToString( <AppContainer> <AppProvider context={context}> <AppContent /> </AppProvider> </AppContainer>, ); ... }, ... }; ...
客戶端的稍微複雜一些,由於涉及熱重載因此renderApp函數須要作些改造,從無參改成接收組件做爲參數:
./src/client/index.tsx ... import { AppContainer } from 'react-hot-loader'; ... function renderApp(Comp) { ReactDOM.hydrate( <AppContainer warnings={false}> <Comp /> </AppContainer>, document.getElementById('app'), ); } ... window.onload = () => { renderApp(App); if (module.hot) { module.hot.accept('./component/app', () => renderApp(require('./component/app').default)); } }; ...
這樣,咱們就實現了客戶端的熱重載效果,打開瀏覽器,咱們修改AppContent組件裏的hello world字符串,會發現瀏覽器無刷新的呈現了最新結果,修改樣式文件也會實時應用變動內容。
利用nodemon,能夠很方便的實現重啓node進程。
npm install nodemon --save-dev
咱們在根目錄下新建nodemon的配置文件nodemon.json:
./nodemon.json { "watch": [ "./dist/config", "./dist/server", "./dist/webpack" ] }
咱們監聽dist目錄下的三個文件夾,這三個文件夾內容涉及服務端代碼。一旦咱們修改了ts文件,ts編譯成的js就會發生相應的修改,從而被nodemon監聽到。
修改咱們的啓動命令:
{ ... "scripts": { ... "dev": "nodemon ./dist/server/index.js", ... }, ... }
將node修改成nodemon便可。
這樣就實現了修改服務端代碼,自動重啓了。
雖然按照上述方法,咱們實現了服務端自動重啓,可是咱們已經打開瀏覽器並感知不到服務端重啓這個事件,咱們想要實現感知能夠利用webpack-hot-middleware(下簡稱whm)來實現。
從瀏覽器控制檯的打印信息能夠看到,當服務端重啓,whm客戶端會丟失連接,並定時從新嘗試連接,直到成功。咱們能夠利用這一特性來實現瀏覽器自動刷新,咱們在服務端啓動時設置一個hmrKey值,並在服務端bundle完成後經過whm的publish方法向瀏覽器定時推送該值,瀏覽器則進行監聽,將該值存於本地,一旦服務端重啓,hmrKey改變,瀏覽器接收到新的hmrKey值則進行刷新頁面操做。
咱們本身寫的koa-webpack-hot-middleware輸出的是一個function,如今咱們要利用whm的實例,因此咱們將其做爲function的屬性一併輸出。
// ./src/webpack/koa-webpack-hot-middleware.ts ... export default (...) => { ... const koaWebpackHotMiddleware = (...) => { ... }; ... (koaWebpackHotMiddleware as any).hotMiddleware = hotMiddleware; ... return koaWebpackHotMiddleware; ... }; ...
./src/webpack/webpack-dev-server.ts ... export default (...) => { ... let hotMiddleware; ... clientCompiler.plugin('done', () => { ... serverCompiler.plugin('done', () => serverCompilerDone.call(null, hotMiddleware)); ... }; ... const koaWebpackHotMiddlewareObject = koaWebpackHotMiddleware(clientCompiler, { heartbeat: 1000, }); ... hotMiddleware = (koaWebpackHotMiddlewareObject as any).hotMiddleware; ... app.use(koaWebpackHotMiddlewareObject); ... }; ...
咱們先定義whm實例變量名,配置完clientCompiler後,再利用其得到whm實例值,最後給whm實例變量賦值。
在入口處設定,並經過whm實例發送給瀏覽器:
// ./src/server/index.ts ... const hmrKey = Math.random() * 100000 + ''; let hmrKeyT; ... if (isDev) { ... webpackDevServer(app, (hotMiddleware) => { ... if (hotMiddleware && typeof hotMiddleware.publish === 'function') { global.clearInterval(hmrKeyT); hmrKeyT = global.setInterval(() => { hotMiddleware.publish({ action: 'bundled', hmrKey }); }, 1000); } ... }); // 僅在開發環境使用 ... } ...
客戶端入口進行監聽邏輯:
// ./src/client/index.tsx ... window.onload = () => { ... if (module.hot) { ... let hmrKey; ... /* tslint:disable no-submodule-imports */ const hotClient = require('webpack-hot-middleware/client'); ... hotClient.subscribe((e) => { if (e.action === 'bundled') { if (hmrKey && (hmrKey !== e.hmrKey)) { window.location.reload(); } else { hmrKey = e.hmrKey; } } }); ... } }; ...
根據官方建議該值爲心跳時長兩倍,上面咱們設置1秒一次心跳,這裏咱們設置爲兩秒:
// ./src/webpack/client.ts ... ((clientDevConfig.entry as any).client as string[]).unshift( 'webpack-hot-middleware/client?timeout=2000', ); // 熱重載配置 ...
業界經常使用的有browser-sync,我也嘗試了一下,可是對於咱們這個koa+webpack體系的app並非太適合,它依賴express,還得額外加gulp來進行流程控制,進行一大堆繁雜的配置,另外它的功能實在太強大,因此這裏我就本身想了上面這個解決方案。
By devlee