從零開始構建react應用(七)代碼修改自動應用

前言

開發的時候,爲了更好的體驗,咱們每每會但願修改完代碼,按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

建立基於koa的hot中間件

和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

修改clientDevConfig

按照官方介紹,使用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

給根節點包裹AppContainer

根據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的配置文件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值則進行刷新頁面操做。

輸出whm實例

咱們本身寫的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;
  ...
};
...

將whm實例做爲serverCompilerDone回調參數

./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實例變量賦值。

hmrKey發送

在入口處設定,並經過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);
    }
    ...
  }); // 僅在開發環境使用
  ...
}
...

hmrKey接收

客戶端入口進行監聽邏輯:

// ./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來進行流程控制,進行一大堆繁雜的配置,另外它的功能實在太強大,因此這裏我就本身想了上面這個解決方案。

Thanks

By devlee

相關文章
相關標籤/搜索