フロントエンドの開発のためのライブリロードサーバの作成


私は私の最初の数年間のWeb開発Altのタブを私のブラウザに手動で私はコードに変更を行うたびにページをリフレッシュするためにオーバーを費やした.私は近代的なフレームワーク(Vueと反応)を使用し始めたまで、これがどれだけ私を悩ましたかを理解しませんでした.私は、それがものであったということさえ知りませんでした、しかし、現在、私は決して戻ることができません.
あなたが現在自動再読み込みなしでウェブページを作っているならば、すべてを落として、現在フックされてください!

ホットリロード
どのような自動再読み込みですか?
要するに、あなたのコードへの変更のための開発サーバーの時計を持って、次に、レンダリングされたページがあなたのコードと同期しているように、それ自体をリフレッシュするためにあなたのブラウザーに話しています.
任意のソースファイルが変更されるたびにページが完全に再読み込みを行う場合、それはすでに有用です.しかし、物事はそれよりはるかにおかしくなります.
VueとReactionのような現代のフレームワークは、彼らが「熱い」再ローディングと呼ぶものを持っています.ホットリロードの目標は、ページ全体の代わりに、何かが変更されたときに、ページ上のコンテンツの最小の部分を更新することです.あなたのウェブサイトの状態(フォームのコンテンツ、ウィジェットのオープン/クローズド状態など)の残りの部分は、あなたが変更したものだけがページ上で変更されますが、これは素晴らしいです.

棚をホットリロードサーバーを取得
幸いなことに、非常にスマートな人々はあなたのためのホットリロード問題を解決しました.あなたが使用する既存のソリューションは、プロジェクトに依存しますが、最新のWebdevを使用している場合は、フレームワークやWebPackのようなモジュールバンドル(ホットパック/Live Reload Server)を使用している可能性があります.そうでなければ、いくつかの程度または別のトリックを行ういくつかのオープンソースプロジェクトにGoglingのビットを取得します.

または独自にロールバック!
あなたは間違いなくオフの棚、深刻な開発作業のためのホットリロードサーバーを取得する必要があります.しかし、それはあなた自身を作るために楽しい運動であり、プロセスの謎の一部を削除します.ソースファイルを変更するたびに、ブラウザをリフレッシュするシンプルなLive Reading開発サーバーを作成します.
サーバがブラウザにリロードを引き起こすように通知する方法には、2つの合理的な選択肢があります.

  • ポーリング我々は、サイトが何かが変わったかどうか尋ねるために100ミリ秒(またはそれ以上)ごとにpingしているプレーンな古いバニラHTTPルートをセットすることができました.

  • Websockets . サーバーが情報をブラウザにプッシュできるように、我々は2ウェイWebSocketを設定することができました.そのように、変化が変化して、サイトの上で見えるようになっているその変化はありません.
  • WebSocketsはより楽しいですし、将来的に多くの柔軟性を与えます(このライブリローナを何か熱いものに変えたい場合)、そのルートに行こう.最終的には何が変わったかを知るのに役に立つかもしれませんが、始めるには、何かが変化し、結果としてサイトをリロードすることを知っておく必要があります.
    以下に簡単なアプローチを示します.
  • 開発HTTPサーバーと一緒にWebSocketサーバーを実行します.
  • devサーバを起動するnodemon ソースファイルが変更されるたびに再起動されます.
  • WebSocketクライアントをオープンしたHTMLファイルにスクリプトを挿入します.接続が終了すると、そのスクリプトはページを再読み込みします.
  • 集合的に、ソースファイルへのどんな変更もサーバーを再起動させて、したがって、ブラウザでロードされたどんなページにもWebSocket接続を壊すようにします.これらの接続を切断すると、ページはすぐに再読み込みされ、新しく再起動されたサーバーへの新しい接続を確立します.したがって、ライブリロード!
    「注入」は何か空想のように聞こえるが、ここでは、「注入された」コードを包むよりも、それ以上ではないことに注意してください<script/> サーバーがブラウザに送信するときにHTML文書の最後にタグを追加します.HTMLパーサーがそうであるので、これは働きます.確かに、スクリプトのタグはすべての中にある必要があります<html/> タグが、それらがない場合はブラウザがとにかく実行されます.
    今それはコードのための時間です.

    依存

  • nodemon ファイルの変更を検出し、結果としてサーバーを再起動します.( npm install nodemon )

  • ws サーバ側のウェブソケットを設定するには.( npm install ws )

  • Node.js 開発サーバーの実行.私はV 14を使用していますoptional chaining

  • クライアント側注入コード
    私はWebSocket接続を開始する死んだ単純なスクリプトから始めましたclose イベントが発生した場合、そのページを再読み込みします.
    /**
     * @file site/client-websocket.js
     */
    const socket = new WebSocket('ws://localhost:8090');
    socket.addEventListener('close',()=>{
      location.reload();
    });
    
    そのスクリプトは簡単すぎた.コアの欠陥は以下の通りです.
  • それは世界的な名前空間を汚染するsocket 変数名はドキュメントスコープ内の何かによって使用されるかもしれません.
  • サーバーが十分に速くページを再起動しないならば、ページは再ロードすることができません、そして、あなたは手動でリフレッシュしなければならない死んだページで立ち往生しています.
  • 最初の問題を解決するには、コードをIffeでラップできます"Immediately Invoked Function Expression" ). 第2の問題を解決するためには、リトライを得るためにもう少し複雑さが必要です.その結果:
    /**
     * @file site/client-websocket.js
     */
    (()=>{
      const socketUrl = 'ws://localhost:8090';
      let socket = new WebSocket(socketUrl);
      socket.addEventListener('close',()=>{
        // Then the server has been turned off,
        // either due to file-change-triggered reboot,
        // or to truly being turned off.
    
        // Attempt to re-establish a connection until it works,
        // failing after a few seconds (at that point things are likely
        // turned off/permanantly broken instead of rebooting)
        const interAttemptTimeoutMilliseconds = 100;
        const maxDisconnectedTimeMilliseconds = 3000;
        const maxAttempts = Math.round(maxDisconnectedTimeMilliseconds/interAttemptTimeoutMilliseconds);
        let attempts = 0;
        const reloadIfCanConnect = ()=>{
          attempts ++ ;
          if(attempts > maxAttempts){
            console.error("Could not reconnect to dev server.");
            return;
          }
          socket = new WebSocket(socketUrl);
          socket.addEventListener('error',()=>{
            setTimeout(reloadIfCanConnect,interAttemptTimeoutMilliseconds);
          });
          socket.addEventListener('open',()=>{
            location.reload();
          });
        };
        reloadIfCanConnect();
      });
    })();
    

    開発サーバコード
    あなたが長い方法を行う場合は、Expressのようなフレームワークを使用せずに.js
    /** @file site/dev-server.js */
    const http = require('http');
    const fs = require('fs');
    const path = require('path');
    const WebSocket = require('ws');
    
    const HTTP_PORT = 8089;
    const WEBSOCKET_PORT = 8090;
    const CLIENT_WEBSOCKET_CODE = fs.readFileSync(path.join(__dirname,'client-websocket.js'),'utf8');
    
    // Websocket server (for allowing browser and dev server to have 2-way communication)
    // We don't even need to do anything except create the instance!
    const wss = new WebSocket.Server({
      port: WEBSOCKET_PORT
    });
    
    /**
     * @typedef {import('http').IncomingMessage} req
     * @typedef {import('http').ServerResponse} res
    */
    
    /** Use classic server-logic to serve a static file (e.g. default to 'index.html' etc)
     * @param {string} route
     * @param {res} res
     * @returns {boolean} Whether or not the page exists and was served
     */
    function serveStaticPageIfExists(route,res) {
      // We don't care about performance for a dev server, so sync functions are fine.
      // If the route exists it's either the exact file we want or the path to a directory
      // in which case we'd serve up the 'index.html' file.
      if(fs.existsSync(route)){
        if(fs.statSync(route).isDirectory()){
          return serveStaticPageIfExists(path.join(route,'index.html'),res);
        }
        else if(fs.statSync(route).isFile()){
          res.writeHead(200);
          /** @type {string|Buffer} */
          let file = fs.readFileSync(route);
          if(route.endsWith('.html')){
            // Inject the client-side websocket code.
            // This sounds fancier than it is; simply
            // append the script to the end since
            // browsers allow for tons of deviation
            // from *technically correct* HTML.
            file = `${file.toString()}\n\n<script>${CLIENT_WEBSOCKET_CODE}</script>`;
          }
          res.end(file);
          return true;
        }
      }
      return false;
    }
    
    /** General request handler and router
     * @param {req} req
     * @param {res} res
    */
    const requestHandler = function (req, res) {
      const method = req.method.toLowerCase();
      if(method=='get'){
        // No need to ensure the route can't access other local files,
        // since this is for development only.
        const route = path.normalize(path.join(__dirname,'src',req.url));
        if(serveStaticPageIfExists(route,res)){
          return;
        }
      }
      res.writeHead(404);
      res.end();
    }
    
    const server = http.createServer(requestHandler);
    server.listen(HTTP_PORT);
    
    あなたのWebサーバーを効率的にフレームワークでより多くのコードを作ることができることに注意してくださいExpress.js (というのは、たぶん)しかし、時にはノードに組み込まれているものを作ることもあります.

    すべてのランニング
    最後に、このデーモンを使用してこのサーバーを実行します.
    npx nodemon ./site/dev-server.js --ext js,html,css,md
    
    ブラウザタブを開きますhttp://localhost:8089 そして、それ!今、私たちはライブリロードを持つ死んだシンプルな開発サーバーがあります.