91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

webpack-dev-server的核心概念以及熱加載

發布時間:2021-09-09 07:53:07 來源:億速云 閱讀:258 作者:chen 欄目:開發技術

這篇文章主要講解了“webpack-dev-server的核心概念以及熱加載”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“webpack-dev-server的核心概念以及熱加載”吧!

webpack-dev-server 核心概念

Webpack 的 ContentBase vs publicPath vs output.path

webpack-dev-server 會使用當前的路徑作為請求的資源路徑(所謂

當前的路徑

就是運行 webpack-dev-server 這個命令的路徑,如果對 webpack-dev-server 進行了包裝,比如 wcf,那么當前路徑指的就是運行 wcf 命令的路徑,一般是項目的根路徑),但是讀者可以通過指定 content-base 來修改這個默認行為:

webpack-dev-server --content-base build/

這樣 webpack-dev-server 就會使用 build 目錄下的資源來處理靜態資源的請求,如 css/ 圖片等。content-base 一般不要和 publicPath、output.path 混淆掉。其中 content-base 表示靜態資源的路徑是什么,比如下面的例子:

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <link rel="stylesheet" type="text/css" href="index.css" rel="external nofollow" >
</head>
<body>
  <div id="react-content">這里要插入 js 內容</div>
</body>
</html>

在作為 html-webpack-plugin 的 template 以后,那么上面的 index.css 路徑到底是什么?是相對于誰來說?上面已經強調了:如果在沒有指定 content-base 的情況下就是相對于當前路徑來說的,所謂的當前路徑就是在運行 webpack-dev-server 目錄來說的,所以假如在項目根路徑運行了這個命令,那么就要保證在項目根路徑下存在該 index.css 資源,否則就會存在 html-webpack-plugin 的 404 報錯。當然,為了解決這個問題,可以將 content-base 修改為和 html-webpack-plugin的html 模板一樣的目錄。

上面講到 content-base 只是和靜態資源的請求有關,那么我們將其 publicPath 和 output.path 做一個區分。
首先:假如將 output.path 設置為build(這里的 build 和 content-base 的 build 沒有任何關系,請不要混淆),要知道 webpack-dev-server 實際上并沒有將這些打包好的 bundle 寫到這個目錄下,而是存在于內存中的,但是我們可以假設(注意這里是假設)其是寫到這個目錄下的。
然后:這些打包好的 bundle 在被請求的時候,其路徑是相對于配置的publicPath來說的,publicPath 相當于虛擬路徑,其映射于指定的output.path。假如指定的 publicPath 為 "/assets/",而且 output.path 為 "build",那么相當于虛擬路徑 "/assets/" 對應于 "build"(前者和后者指向的是同一個位置),而如果 build 下有一個 "index.css",那么通過虛擬路徑訪問就是/assets/index.css。
最后:如果某一個內存路徑(文件寫在內存中)已經存在特定的 bundle,而且編譯后內存中有新的資源,那么我們也會使用新的內存中的資源來處理該請求,而不是使用舊的 bundle!比如有一個如下的配置:

module.exports = {
  entry: {
    app: ["./app/main.js"]
  },
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/assets/",
    //此時相當于/assets/路徑對應于 build 目錄,是一個映射的關系
    filename: "bundle.js"
  }
}

那么我們要訪問編譯后的資源可以通過 localhost:8080/assets/bundle.js 來訪問。如果在 build 目錄下有一個 html 文件,那么可以使用下面的方式來訪問 js 資源:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <script src="assets/bundle.js"></script>
</body>
</html>

此時會看到控制臺輸出如下內容:

webpack-dev-server的核心概念以及熱加載enter image description here

主要關注下面兩句輸出:

Webpack result is served from /assets/

Content is served from /users/…./build

之所以是這樣的輸出結果是因為設置了 contentBase 為 build,因為運行的命令為webpack-dev-server --content-base build/。所以,一般情況下:如果在 html 模板中不存在對外部相對資源的引用,我們并不需要指定 content-base,但是如果存在對外部相對資源 css/ 圖片的引用,可以通過指定 content-base 來設置默認靜態資源加載的路徑,除非所有的靜態資源全部在當前目錄下

webpack-dev-server 熱加載(HMR)

為 webpack-dev-server 開啟 HMR 模式只需要在命令行中添加--hot,它會將 HotModuleReplacementPlugin 這個插件添加到 webpack 的配置中去,所以開啟 HotModuleReplacementPlugin 最簡單的方式就是使用 inline 模式。在 inline 模式下,只需要在命令行中添加--inline --hot就可以自動實現。
這時候 webpack-dev-server 就會自動添加 webpack/hot/dev-server 入口文件到配置中,只是需要訪問下面的路徑就可以了 http://?host?:?port?/?path?。在控制臺中可以看到如下的內容
其中以 [HMR] 開頭的部分來自于 webpack/hot/dev-server 模塊,而以[WDS]開頭的部分來自于 webpack-dev-server 的客戶端。下面的部分來自于 webpack-dev-server/client/index.js 內容,其中的 log 都是以 [WDS] 開頭的:

function reloadApp() {
  if(hot) {
    log("info", "[WDS] App hot update...");
    window.postMessage("webpackHotUpdate" + currentHash, "*");
  } else {
    log("info", "[WDS] App updated. Reloading...");
    window.location.reload();
  }
}

而在 webpack/hot/dev-server 中的 log 都是以 [HMR] 開頭的(它是來自于 Webpack 本身的一個 plugin):

if(!updatedModules) {
        console.warn("[HMR] Cannot find update. Need to do a full reload!");
        console.warn("[HMR] (Probably because of restarting the webpack-dev-server)");
        window.location.reload();
        return;
      }

那么如何在 nodejs 中使用 HMR 功能呢?此時需要修改三處配置文件:

1.添加一個 Webpack 的入口點,也就是 webpack/hot/dev-server
2.添加一個 new webpack.HotModuleReplacementPlugin() 到 webpack 的配置中
3.添加 hot:true 到 webpack-dev-server 配置中,從而在服務端啟動 HMR(可以在 cli 中使用 webpack-dev-server --hot)
比如下面的代碼就展示了 webpack-dev-server 為了實現 HMR 是如何處理入口文件的:

if(options.inline) {
  var devClient = [require.resolve("../client/") + "?" + protocol + "://" + (options.public || (options.host + ":" + options.port))];
  //將 webpack-dev-server 的客戶端入口添加到的 bundle 中,從而達到自動刷新
  if(options.hot)
    devClient.push("webpack/hot/dev-server");
    //這里是 webpack-dev-server 中對 hot 配置的處理
  [].concat(wpOpt).forEach(function(wpOpt) {
    if(typeof wpOpt.entry === "object" && !Array.isArray(wpOpt.entry)) {
      Object.keys(wpOpt.entry).forEach(function(key) {
        wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]);
      });
    } else {
      wpOpt.entry = devClient.concat(wpOpt.entry);
    }
  });
}

滿足上面三個條件的 nodejs 使用方式如下:

var config = require("./webpack.config.js");
config.entry.app.unshift("webpack-dev-server/client?http://localhost:8080/", "webpack/hot/dev-server");
//條件一(添加了 webpack-dev-server 的客戶端和 HMR 的服務端)
var compiler = webpack(config);
var server = new webpackDevServer(compiler, {
  hot: true //條件二(--hot 配置,webpack-dev-server 會自動添加 HotModuleReplacementPlugin)
  ...
});
server.listen(8080);

webpack-dev-server 啟動 proxy 代理

webpack-dev-server 使用

http-proxy-middleware

去把請求代理到一個外部的服務器,配置的樣例如下:

proxy: {
  '/api': {
    target: 'https://other-server.example.com',
    secure: false
  }
}
// In webpack.config.js
{
  devServer: {
    proxy: {
      '/api': {
        target: 'https://other-server.example.com',
        secure: false
      }
    }
  }
}
// Multiple entry
proxy: [
  {
    context: ['/api-v1/**', '/api-v2/**'],
    target: 'https://other-server.example.com',
    secure: false
  }
]

這種代理在很多情況下是很重要的,比如可以把一些靜態文件通過本地的服務器加載,而一些 API 請求全部通過一個遠程的服務器來完成。還有一個情景就是在兩個獨立的服務器之間進行請求分割,如一個服務器負責授權而另外一個服務器負責應用本身。下面給出日常開發中遇到的一個例子:

(1)有一個請求是通過相對路徑來完成的,比如地址是 "/msg/show.htm"。但是,在日常和生產環境下前面會加上不同的域名,如日常是 you.test.com 而生產環境是 you.inc.com。

(2)那么比如現在想在本地啟動一個 webpack-dev-server,然后通過 webpack-dev-server 來訪問日常的服務器,而且日常的服務器地址是 11.160.119.131,所以會通過如下的配置來完成:

devServer: {
    port: 8000,
    proxy: {
      "/msg/show.htm": {
        target: "http://11.160.119.131/",
        secure: false
      }
    }
  }

此時當請求 "/msg/show.htm" 的時候,其實請求的真實 URL 地址為 "http//11.160.119.131/msg/show.htm"。

(3)在開發環境中遇到一個問題,那就是:如果本地的 devServer 啟動的地址為: "http://30.11.160.255:8000/" 或者常見的 "http://0.0.0.0:8000/" ,那么真實的服務器會返回一個 URL 要求登錄,但是,將本地 devServer 啟動到 localhost 上就不存在這個問題了(一個可能的原因在于 localhost 種上了后端需要的 cookie,而其他的域名沒有種上 cookie,導致代理服務器訪問日常服務器的時候沒有相應的 cookie,從而要求權限驗證)。其中指定 localhost 的方式可以通過

wcf

來完成,因為 wcf 默認可以支持 IP 或者 localhost 方式來訪問。當然也可以通過添加下面的代碼來完成:

devServer: {
    port: 8000,
    host:'localhost',
    proxy: {
      "/msg/show.htm": {
        target: "http://11.160.119.131/",
        secure: false
      }
    }
  }

(4)關于 webpack-dev-server 的原理,讀者可以查看“反向代理為何叫反向代理”等資料來了解,其實正向代理和反向代理用一句話來概括就是:“正向代理隱藏了真實的客戶端,而反向代理隱藏了真實的服務器”。而 webpack-dev-server 其實扮演了一個代理服務器的角色,服務器之間通信不會存在前端常見的同源策略,這樣當請求 webpack-dev-server 的時候,它會從真實的服務器中請求數據,然后將數據發送給你的瀏覽器。

  browser => localhost:8080(webpack-dev-server無代理) => http://you.test.com
  browser => localhost:8080(webpack-dev-server有代理) => http://you.test.com

上面的第一種情況就是沒有代理的情況,在 localhost:8080 的頁面通過前端策略去訪問 http://you.test.com 會存在同源策略,即第二步是通過前端策略去訪問另外一個地址的。但是對于第二種情況,第二步其實是通過代理去完成的,即服務器之間的通信,不存在同源策略問題。而我們變成了直接訪問代理服務器,代理服務器返回一個頁面,對于頁面中某些滿足特定條件前端請求(proxy、rewrite配置)全部由代理服務器來完成,這樣同源問題就通過代理服務器的方式得到了解決。

(5)上面講述的是 target 是 IP 的情況,如果 target 要指定為域名的方式,可能需要綁定 host。比如下面綁定的 host:
11.160.119.131 youku.min.com
那么下面的 proxy 配置就可以采用域名了:

devServer: {
    port: 8000,
    proxy: {
      "/msg/show.htm": {
        target: "http://youku.min.com/",
        secure: false
      }
    }
  }

這和 target 綁定為 IP 地址的效果是完全一致的。總結一句話:“target 指定了滿足特定 URL 的請求應該對應到哪臺主機上,即代理服務器應該訪問的真實主機地址”。
其實 proxy 還可以通過配置一個 bypass() 函數的返回值視情況繞開一個代理。這個函數可以查看 HTTP 請求和響應及一些代理的選項。它返回要么是 false 要么是一個 URL 的 path,這個 path 將會用于處理請求而不是使用原來代理的方式完成。下面例子的配置將會忽略來自于瀏覽器的 HTTP 請求,它和 historyApiFallback 配置類似。瀏覽器請求可以像往常一樣接收到 html 文件,但是 API 請求將會被代理到另外的服務器:

proxy: {
  '/some/path': {
    target: 'https://other-server.example.com',
    secure: false,
    bypass: function(req, res, proxyOptions) {
      if (req.headers.accept.indexOf('html') !== -1) {
        console.log('Skipping proxy for browser request.');
        return '/index.html';
    }
  }
 }
}

對于代理的請求也可以通過提供一個函數來重寫,這個函數可以查看或者改變 HTTP 請求。下面的例子就會重寫 HTTP 請求,其主要作用就是移除 URL 前面的 /api 部分。

proxy: {
  '/api': {
    target: 'https://other-server.example.com',
    pathRewrite: {'^/api' : ''}
  }
}

其中 pathRewrite 配置來自于 http-proxy-middleware。更多配置可以查看

http-proxy-middleware 官方文檔。

historyApiFallback 選項

當使用 HTML 5 的 history API 的時候,當 404 出現的時候可能希望使用 index.html 來作為請求的資源,這時候可以使用這個配置 :historyApiFallback:true。然而,如果修改了 output.publicPath,就需要指定重定向的 URL,可以使用 historyApiFallback.index 選項。

// output.publicPath: '/foo-app/'
historyApiFallback: {
  index: '/foo-app/'
}

使用 rewrite 選項可以重新設置靜態資源

historyApiFallback: {
    rewrites: [
        // shows views/landing.html as the landing page
        { from: /^\/$/, to: '/views/landing.html' },
        // shows views/subpage.html for all routes starting with /subpage
        { from: /^\/subpage/, to: '/views/subpage.html' },
        // shows views/404.html on all other pages
        { from: /./, to: '/views/404.html' },
    ],
},

使用 disableDotRule 來滿足一個需求,即如果一個資源請求包含一個
.符號,那么表示是對某一個特定資源的請求,也就滿足 dotRule。我們看看
connect-history-api-fallback 內部是如何處理的:

if (parsedUrl.pathname.indexOf('.') !== -1 &&
        options.disableDotRule !== true) {
      logger(
        'Not rewriting',
        req.method,
        req.url,
        'because the path includes a dot (.) character.'
      );
      return next();
    }
    rewriteTarget = options.index || '/index.html';
    logger('Rewriting', req.method, req.url, 'to', rewriteTarget);
    req.url = rewriteTarget;
    next();
  };

也就是說,如果是對絕對資源的請求,也就是滿足 dotRule,但是 disableDotRule(disable dot rule file request)為 false,表示我們會自己對滿足 dotRule 的資源進行處理,所以不用定向到 index.html 中!如果 disableDotRule 為 true 表示不會對滿足 dotRule 的資源進行處理,所以直接定向到 index.html!

history({
  disableDotRule: true
})

webpack-dev-server 更多配置

var server = new WebpackDevServer(compiler, {
  contentBase: "/path/to/directory",
  //content-base 配置 
  hot: true,
  //開啟 HMR,由 webpack-dev-server 發送 "webpackHotUpdate" 消息到客戶端代碼
  historyApiFallback: false,
  //單頁應用 404 轉向 index.html
  compress: true,
  //開啟資源的 gzip 壓縮
  proxy: {
    "**": "http://localhost:9090"
  },
  //代理配置,來源于 http-proxy-middleware
  setup: function(app) {
     //webpack-dev-server 本身是 Express 服務器可以添加自己的路由
    // app.get('/some/path', function(req, res) {
    //   res.json({ custom: 'response' });
    // });
  },
  //為 Express 服務器的 express.static 方法配置參數 http://expressjs.com/en/4x/api.html#express.static
  staticOptions: {
  },
  //在 inline 模式下用于控制在瀏覽器中打印的 log 級別,如`error`, `warning`, `info` or `none`.
  clientLogLevel: "info",
  //不在控制臺打印任何 log
  quiet: false,
  //不輸出啟動 log
  noInfo: false,
  //webpack 不監聽文件的變化,每次請求來的時候重新編譯
  lazy: true,
  //文件名稱
  filename: "bundle.js",
  //webpack 的 watch 配置,每隔多少秒檢查文件的變化
  watchOptions: {
    aggregateTimeout: 300,
    poll: 1000
  },
  //output.path 的虛擬路徑映射
  publicPath: "/assets/",
  //設置自定義 http 頭
  headers: { "X-Custom-Header": "yes" },
  //打包狀態信息輸出配置
  stats: { colors: true },
  //配置 https 需要的證書等
  https: {
    cert: fs.readFileSync("path-to-cert-file.pem"),
    key: fs.readFileSync("path-to-key-file.pem"),
    cacert: fs.readFileSync("path-to-cacert-file.pem")
  }
});
server.listen(8080, "localhost", function() {});
// server.close();

上面其他配置中,除了 filename 和 lazy 外都是容易理解的,那么下面繼續分析下 lazy 和 filename 的具體使用場景。我們知道,在 lazy 階段 webpack-dev-server 不是調用 compiler.watch 方法,而是等待請求到來的時候才會編譯。源代碼如下:

startWatch: function() {
      var options = context.options;
      var compiler = context.compiler;
      // start watching
      if(!options.lazy) {
        var watching = compiler.watch(options.watchOptions, share.handleCompilerCallback);
        context.watching = watching;
        //context.watching 得到原樣返回的 Watching 對象
      } else {
       //如果是 lazy,表示我們不是 watching 監聽,而是請求的時候才編譯
        context.state = true;
      }
    }

調用 rebuild 的時候會判斷 context.state。每次重新編譯后在 compiler.done 中會將 context.state 重置為 true!

rebuild: function rebuild() {
      //如果沒有通過 compiler.done 產生過 Stats 對象,那么設置 forceRebuild 為 true
      //如果已經有 Stats 表明以前 build 過,那么調用 run 方法
      if(context.state) {
        context.state = false;
        //lazy 狀態下 context.state 為 true,重新 rebuild
        context.compiler.run(share.handleCompilerCallback);
      } else {
        context.forceRebuild = true;
      }
    },

下面是當請求到來的時候我們調用上面的 rebuild 繼續重新編譯:

handleRequest: function(filename, processRequest, req) {
      // in lazy mode, rebuild on bundle request
      if(context.options.lazy && (!context.options.filename || context.options.filename.test(filename)))
        share.rebuild();
      //如果 filename 里面有 hash,那么通過 fs 從內存中讀取文件名,同時回調就是直接發送消息到客戶端!!!
      if(HASH_REGEXP.test(filename)) {
        try {
          if(context.fs.statSync(filename).isFile()) {
            processRequest();
            return;
          }
        } catch(e) {
        }
      }
      share.ready(processRequest, req);
      //回調函數將文件結果發送到客戶端
    },

其中 processRequest 就是直接把編譯好的資源發送到客戶端:

function processRequest() {
      try {
        var stat = context.fs.statSync(filename);
        //獲取文件名
        if(!stat.isFile()) {
          if(stat.isDirectory()) {
            filename = pathJoin(filename, context.options.index || "index.html");
            //文件名
            stat = context.fs.statSync(filename);
            if(!stat.isFile()) throw "next";
          } else {
            throw "next";
          }
        }
      } catch(e) {
        return goNext();
      }
      // server content
      // 直接訪問的是文件那么讀取,如果是文件夾那么要訪問文件夾
      var content = context.fs.readFileSync(filename);
      content = shared.handleRangeHeaders(content, req, res);
      res.setHeader("Access-Control-Allow-Origin", "*"); 
      // To support XHR, etc.
      res.setHeader("Content-Type", mime.lookup(filename) + "; charset=UTF-8");
      res.setHeader("Content-Length", content.length);
      if(context.options.headers) {
        for(var name in context.options.headers) {
          res.setHeader(name, context.options.headers[name]);
        }
      }
      // Express automatically sets the statusCode to 200, but not all servers do (Koa).
      res.statusCode = res.statusCode || 200;
      if(res.send) res.send(content);
      else res.end(content);
    }
  }

所以,在 lazy 模式下如果我們沒有指定文件名 filename,即每次請求的是那個 Webpack 輸出文件(chunk),那么每次都是會重新 rebuild 的!但是如果指定了文件名,那么只有訪問該文件名的時候才會 rebuild!

感謝各位的閱讀,以上就是“webpack-dev-server的核心概念以及熱加載”的內容了,經過本文的學習后,相信大家對webpack-dev-server的核心概念以及熱加載這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

临颍县| 乳源| 开阳县| 长沙市| 永靖县| 包头市| 永济市| 太湖县| 台北市| 台中县| 沙洋县| 哈尔滨市| 武安市| 尼玛县| 雅江县| 龙海市| 怀柔区| 香格里拉县| 资讯| 菏泽市| 阿合奇县| 普陀区| 济源市| 商洛市| 柯坪县| 富蕴县| 青海省| 陕西省| 新兴县| 宁远县| 枣庄市| 寿光市| 谷城县| 奉化市| 吉木萨尔县| 德惠市| 哈密市| 治县。| 法库县| 荣昌县| 宁海县|