Core concepts of webpack-dev-server

Keywords: Front-end Webpack React hot update JSON

Core concepts of webpack-dev-server

ContentBase vs publicPath vs output.path of Web pack

webpack-dev-server uses the current path as the requested resource path (so-called

Current Path

This is the path to run the command webpack-dev-server, if the webpack-dev-server is wrapped, for example wcf The current path refers to the path to run the wcf command, usually the root path of the project, but the reader can modify the default behavior by specifying content-base:

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

In this way, webpack-dev-server will use the resources in the build directory to handle requests for static resources, such as css/images. Content-base should not be confused with publicPath and output.path. Where content-base represents the path of a static resource, such as the following example:

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
  <div id="react-content">Here I want to insert js content</div>
</body>
</html>

After being a template for html-webpack-plugin, what exactly is the index.css path above? To whom? It has been emphasized above: if the content-base is not specified, the so-called current path is relative to the current path, which is running the webpack-dev-server directory, so if the command is run in the project root path, then ensure that the index.css resource exists in the project root path, otherwise there will be html-webpack-plugin. 404 false report. Of course, to solve this problem, you can change content-base to the same directory as the HTML template of html-webpack-plugin.

As mentioned above, content-base is only related to requests for static resources, so let's make a distinction between publicPath and output.path.
First of all: if you set output.path to build (where build has nothing to do with content-base build, don't confuse it), you should know that webpack-dev-server actually does not write these packaged bundle s to this directory, but exists in memory, but we can assume (note here is the assumption) that they are written to this directory.
Then: when requested, these packaged bundle s have a path relative to the configurable publicPath, which is equivalent to a virtual path mapped to the specified output.path. If the specified publicPath is "/ assets /" and output.path is "build", then the equivalent virtual path "/ assets /" corresponds to "build" (the former and the latter point to the same location), and if there is an "index.css" under the build, then access through the virtual path is / assets/index.css.
Finally: if a specific bundle already exists in a memory path (file written in memory) and new resources exist in compiled memory, we will also use new resources in memory to process the request instead of using the old bundle! For example, there is a configuration as follows:

module.exports = {
  entry: {
    app: ["./app/main.js"]
  },
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/assets/",
    //In this case, the equivalent / assets / path corresponds to the build directory, which is a mapping relationship.
    filename: "bundle.js"
  }
}

Then we can access the compiled resources through localhost:8080/assets/bundle.js. If you have an html file in the build directory, you can access JS resources in the following way:

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

At this point, you will see the console output as follows:

enter image description here

Focus on the following two sentences:

Webpack result is served from /assets/
Content is served from /users/…./build

The reason for this output is that contentBase is set to build, because the command to run is webpack-dev-server --content-base build/. So, in general, if there is no reference to external relative resources in the html template, we do not need to specify content-base, but if there is a reference to external relative resources css/image, we can set the default static resource loading path by specifying content-base, unless all static resources are in the current directory.

webpack-dev-server hot loading (HMR)

Opening HMR mode for webpack-dev-server only requires adding -- hot to the command line, which adds the HotModule Replacement Plugin plug-in to the configuration of webpack, so the easiest way to open HotModule Replacement Plugin is to use in line mode. In inline mode, it can be automatically implemented by simply adding -- inline --hot to the command line.
At this point, webpack-dev-server will automatically add the webpack/hot/dev-server entry file to the configuration, just need to access the following path to http://host:port/path. You can see the following in the console
The part that begins with [HMR] comes from the webpack/hot/dev-server module, and the part that starts with [WDS] comes from the client of webpack-dev-server. The following sections are from webpack-dev-server/client/index.js content, where log s start with [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();
  }
}

The log s in webpack/hot/dev-server start with [HMR] (it's a plugin from the Webpack itself):

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;
      }

So how to use the HMR function in nodejs? Three configuration files need to be modified at this time:

1. Add a Webpack entry point, that is, webpack/hot/dev-server
2. Add a new webpack. HotModuleReplacement Plugin () to the configuration of webpack
3. Add hot:true to the webpack-dev-server configuration to start HMR on the server side (you can use webpack-dev-server--hot in cli)
For example, the following code shows how webpack-dev-server handles entry files in order to implement HMR:

if(options.inline) {
  var devClient = [require.resolve("../client/") + "?" + protocol + "://" + (options.public || (options.host + ":" + options.port))];
  //Add the client entry of webpack-dev-server to the bundle to achieve automatic refresh
  if(options.hot)
    devClient.push("webpack/hot/dev-server");
    //Here is how to handle the hot configuration in webpack-dev-server
  [].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);
    }
  });
}

Noejs satisfying the above three conditions can be used in the following ways:

var config = require("./webpack.config.js");
config.entry.app.unshift("webpack-dev-server/client?http://localhost:8080/", "webpack/hot/dev-server");
//Conditions 1 (add webpack-dev-server client and HMR server)
var compiler = webpack(config);
var server = new webpackDevServer(compiler, {
  hot: true //Conditional 2 (- hot configuration, webpack-dev-server automatically adds HotModule Replacement Plugin)
  ...
});
server.listen(8080);

webpack-dev-server starts proxy agent

Using webpack-dev-server

http-proxy-middleware

To proxy a request to an external server, configure it as follows:

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
  }
]

This proxy is important in many cases, such as loading some static files through a local server, while some API requests are all completed through a remote server. Another scenario is to split requests between two separate servers, such as one server responsible for authorization and the other server responsible for the application itself. Here's an example of daily development:
(1) A request is made through a relative path, such as'/ msg/show.htm'. However, different domain names will be added to the daily and production environment, such as you.test.com and you.inc.com.

(2) Now, for example, you want to start a webpack-dev-server locally, and then access the daily server through the webpack-dev-server, and the daily server address is 11.160.119.131, so it will be done through the following configuration:

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

When requesting "/msg/show.htm", the actual URL address of the request is "http//11.160.119.131/msg/show.htm".

(3) A problem encountered in the development environment is that if the local devServer starts at the address of "http://30.11.160.255:8000/" or the common "http://0.0.0:8000/", the real server will return a URL to request login, but there is no problem starting the local devServer on the local host (loc LOC LOC LOC is a possible reason). Alhost seeded the cookies needed by the back end, while other domain names did not, resulting in the proxy server accessing the daily server without the corresponding cookies, thus requiring permission validation. The way in which localhost is specified can be used

wcf

To complete, because wcf can support IP or localhost access by default. Of course, it can also be done by adding the following code:

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

(4) With regard to the principle of webpack-dev-server, readers can look at the information such as "why is reverse proxy called reverse proxy". In fact, forward proxy and reverse proxy can be summarized in one sentence: "forward proxy hides the real client while reverse proxy hides the real server". Webpack-dev-server acts as a proxy server. There is no common homology policy in front-end communication between servers. When requesting webpack-dev-server, it requests data from the real server and sends it to your browser.

  browser => localhost:8080(webpack-dev-server No agency) => http://you.test.com
  browser => localhost:8080(webpack-dev-server There are agents.) => http://you.test.com

The first case above is that there is no proxy, which is accessed through the front-end strategy on the localhost:8080 page. http://you.test.com There will be a homology policy, that is, the second step is to access another address through the front-end policy. But in the second case, the second step is actually accomplished by proxy, that is, communication between servers, there is no problem of homology policy. And we become a direct access proxy server. The proxy server returns a page, and some front-end requests (proxy, rewrite configuration) satisfying certain conditions in the page are all completed by the proxy server, so the homology problem is solved by the proxy server.

(5) The above is about target being IP. If target is to be specified as a domain name, it may need to bind host. For example, the following host is bound:
11.160.119.131 youku.min.com
Then the following proxy configuration can use domain name:

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

This is exactly the same as target binding to IP addresses. In conclusion, "target specifies which host the request to satisfy a specific URL should correspond to, that is, the real host address that the proxy server should access."
In fact, proxy can also bypass an agent as appropriate by configuring the return value of a bypass() function. This function allows you to view HTTP requests and responses and some proxy options. It returns either a false path or a path of a URL that will be used to process requests instead of using the original proxy. The configuration of the following example will ignore HTTP requests from browsers, similar to the history ApiFallback configuration. Browser requests can receive html files as usual, but API requests will be proxied to another server:

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';
    }
  }
 }
}

Requests for proxies can also be rewritten by providing a function that can view or change HTTP requests. The following example rewrites the HTTP request, and its main function is to remove the / api section in front of the URL.

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

The path Rewrite configuration comes from http-proxy-middleware. More configurations can be viewed

http-proxy-middleware official document.

HisryApiFallback option

When using the history API of HTML 5, you may want to use index.html as the requested resource when 404 appears, and you can use this configuration: history ApiFallback: true. However, if you modify output.publicPath, you need to specify a redirected URL, using the historyApiFallback.index option.

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

Static resources can be reset using the rewrite option

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' },
    ],
},

Use disableDotRule to satisfy a requirement, that is, if a resource request contains one
Symbols, then, represent a request for a particular resource, which satisfies dotRule. Let's see.
connect-history-api-fallback How to deal with it internally:

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();
  };

That is to say, if the request for absolute resources is to satisfy dotRule, but disableDotRule(disable dot rule file request) is false, it means that we will process the resources satisfying dotRule ourselves, so we do not need to orient to index.html! If disableDotRule is true, it means that resources satisfying dotRule will not be processed, so direct to index.html!

history({
  disableDotRule: true
})

More configurations for webpack-dev-server

var server = new WebpackDevServer(compiler, {
  contentBase: "/path/to/directory",
  //content-base configuration 
  hot: true,
  //Open HMR and send "webpack HotUpdate" message to client code by webpack-dev-server
  historyApiFallback: false,
  //Single page application 404 to index.html
  compress: true,
  //gzip compression of Open Resources
  proxy: {
    "**": "http://localhost:9090"
  },
  //Proxy configuration, from http-proxy-middleware
  setup: function(app) {
     //webpack-dev-server itself is an Express server that can add its own routing
    // app.get('/some/path', function(req, res) {
    //   res.json({ custom: 'response' });
    // });
  },
  //Configure the parameter http://expressjs.com/en/4x/api.html#express.static for Express server's express.static method
  staticOptions: {
  },
  //In inline mode, it is used to control the log level of printing in browsers, such as `error', `warning', `info` or `none'.
  clientLogLevel: "info",
  //Do not print any log s on the console
  quiet: false,
  //No output startup log
  noInfo: false,
  //webpack does not monitor file changes and recompiles each request
  lazy: true,
  //File name
  filename: "bundle.js",
  //watch configuration of webpack, checking file changes every few seconds
  watchOptions: {
    aggregateTimeout: 300,
    poll: 1000
  },
  //Virtual path mapping of output.path
  publicPath: "/assets/",
  //Setting up custom http headers
  headers: { "X-Custom-Header": "yes" },
  //Packing status information output configuration
  stats: { colors: true },
  //Configure the certificates required by https, etc.
  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();

The other configurations above are easy to understand except filename and lazy, so let's continue to analyze the specific usage scenarios of lazy and filename. We know that in the lazy phase, webpack-dev-server does not call the compiler.watch method, but compiles when the request arrives. The source code is as follows:

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 gets the Watching object returned as it is
      } else {
       //If lazy, it means that we are not watching listeners, but compiling when requesting
        context.state = true;
      }
    }

The context.state is judged when rebuild is called. After each recompilation, context.state is reset to true in compiler. dong!

rebuild: function rebuild() {
      //If the Stats object has not been generated through compiler.done, set forceRebuild to true
      //If you already have Stats indicating that you built before, call the run method
      if(context.state) {
        context.state = false;
        //In lazy state, context.state is true, rebuild
        context.compiler.run(share.handleCompilerCallback);
      } else {
        context.forceRebuild = true;
      }
    },

Next, when the request arrives, we call rebuild above to continue recompiling:

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();
      //If there is hash in filename, then read the file name from memory through fs, and call back is to send messages directly to the client!!!
      if(HASH_REGEXP.test(filename)) {
        try {
          if(context.fs.statSync(filename).isFile()) {
            processRequest();
            return;
          }
        } catch(e) {
        }
      }
      share.ready(processRequest, req);
      //Callback functions send file results to the client
    },

ProceRequest sends the compiled resources directly to the client:

function processRequest() {
      try {
        var stat = context.fs.statSync(filename);
        //Get the filename
        if(!stat.isFile()) {
          if(stat.isDirectory()) {
            filename = pathJoin(filename, context.options.index || "index.html");
            //file name
            stat = context.fs.statSync(filename);
            if(!stat.isFile()) throw "next";
          } else {
            throw "next";
          }
        }
      } catch(e) {
        return goNext();
      }
      // server content
      // Direct access is to read the file, if it is a folder, then access the folder
      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);
    }
  }

So, in lazy mode, if we don't specify filename, that is, the Web pack output file (chunk) is requested each time, then rebuild every time! But if you specify a filename, rebuild only if you access the filename! ___________



Author: Dabao 123
Link: https://www.jianshu.com/p/e547fb9747e0
Source: Brief Book
The copyright of the brief book belongs to the author. For any form of reprinting, please contact the author for authorization and indicate the source.

Posted by john_bboy7 on Tue, 14 May 2019 05:04:11 -0700