10
头图
The full text is 3000 words, welcome to like and forward

The thing is like this, a asked me one day: 16164f338c51c5 Why can't see the page I wrote after running my webpack, but:

Um? File list page? Well, I don’t seem to have encountered this situation before, and I can’t give an answer at once, I can only come to the key code:

Focus on the configuration of devServer + HMR webpack.config.js is used, among which:

  • Webpack version is 5.37.0
  • webpack-dev-server version is 3.11.2

After watching for a long time, there is no problem. I gave a few papery suggestions and still couldn't solve the problem, so I just put it down for the time being at the meeting. After a while, the little friend rushed over and told me that after some blind guessing, the problem was solved:

  • output.publicPath = '/' is ok at 06164f338c529b
  • output.publicPath = './' , return to the file list page

Ah? This thing will also affect the effect of devServer , and my instinct tells me it shouldn't.

emmm, I have successfully aroused my curiosity. Although I have written some articles about Webpack source code analysis, webpack-dev-server is really out of my knowledge. Fortunately, I have a book "16164f338c5308 How to read the source code-taking Vetur as an example ", yes Time to show the real technology!

Step 1: Define the problem

First review the process of the problem:

  • webpack.config.js is configured with both ouput.publicPath and devServer
  • Run npx webpack serve start the development server
  • Browser access http://localhost:9000 did not return the user code as expected, but returned to the file list page; but if output.publicPath is restored, everything is as usual

ouput.publicPath be reasonable, 06164f338c53e9 should only affect the path referenced by the final product. Try the command line tool to run curl check the content returned on the homepage:

Tips: Sometimes you can try to bypass the complex logic of the browser and use the simplest tool to verify the content returned by the http request.

As you can see, requesting the http://localhost:9000 returns a large string of html codes, and the title of the page is listing directory -which is the file list page we see:

Although I don't know at which layer it was generated, I am definitely not writing it, and it happened at the HTTP level.

So the core issue is: why Webpack of output.publicPath affect webpack-dev-server operating results of ?

Step 2: Review the background

With questions, I reviewed the official Webpack documentation.

publicPath configuration

First of all, output.publicPath is described as follows:

This is an important option when using on-demand-loading or loading external resources like images, files, etc. If an incorrect value is specified you'll receive 404 errors while loading these resources.

To the effect, this is an option to control on-demand loading or resource file loading. If the corresponding path resource fails to load, it will return 404.

Hey, in fact, this description is very unclear. A simple understanding of output.publicPath will change the path of the product resource in the html file. For example, after Webpack is compiled, the bundle.js file is generated. By default, the path to html is:

<script src="bundle.js" />

If the output.publicPath set, the prefix will be added before the path:

<script src="${output.publicPath}/bundle.js" />

It seems very simple.

devServer configuration item

Let's take a look at the devServer configuration:

This set of options is picked up by webpack-dev-server and can be used to change its behavior in various ways.

devServer is that the 06164f338c5697 configuration will eventually be by 16164f338c569a webpack-dev-server , and webpack-dev-server provides web services including HMR-module hot update.

Feel that the bottom layer of scaffolding tools such as vue-cli and create-react-app depend on webpack-dev-server . You can imagine its role and importance.

Step 3: Analyze the problem

According to the existing intelligence and my understanding of the HTTP protocol, it can be basically inferred that the problem must 16164f338c56f0 webpack-dev-server framework that processes the homepage request. There is a high probability that the output.publicPath attribute affects the judgment logic of the homepage resource. , Resulting in webpack-dev-server can not find the corresponding resource file, return to the bottom file list page.

Well, I think it's reliable, so dig the source code along this line of thought to find out the specific reason.

Step 4: Analyze the code

Structural analysis

The book should be very shallow. You need to look at the source code for debugging. Don’t say anything, first open the 16164f338c5738 webpack-dev-server package to see the content:

Tips: Readers can also try clone webpack-dev-server warehouse code, there are surprises\~\~

The project structure is not complicated. According to the habits of Webpack, it can be inferred that the main code is in the lib directory:

cloc is a very easy to use code statistics tool, official website: https://www.npmjs.com/package/cloc

The amount of code is in the early 2000s, which is fine.

Next, open the package.json file again to see which dependency . After one by one, the dependencies that are strongly related to our problem are:

  • express : No need to introduce the application
  • webpack-dev-middleware : This should not have been noticed by most people. Judging from the official website document, this is a middleware that bridges the Webpack compilation process and express.
  • serve-index : for the file list page in a specific directory! ! !

According to this description, this pot must be serve-index , which feels very close to the answer.

Partial analysis

Entry point: verify the role of the serve-index

After the above analysis, although I don't know where the problem is, I can roughly determine that it is serve-index package. First search for where the webpack-dev-server references this package:

Fortunately, it is only used in the lib/Server.js file. It is much simpler. The static analysis of the call statement can roughly deduce:

  • serveIndex call is wrapped in this.app.use . It is speculated that this.app points to the express example. The use function is used to register the middleware, so the entire serveIndex is a middleware
  • In addition to setupStaticServeIndexFeature , the Server type also contains other setupXXXFeature , which are basically used to add express middleware. The combination of these middleware assembles the HMR, proxy, ssl and other functions provided by webpack-dev-server.

I don’t see anything else. Let’s do a control experiment first, and run dynamically analyze code to verify if there is an error in this place. First insert the debugger serveIndex function, and then:

  • First execute ndb npx webpack serve according to the normal situation, that is, output.publicPath = '/' , the result is that the page is opened as usual, no interruption point is hit, no interruption
  • Then execute ndb npx webpack serve according to ouput.publicPath = './' enter the breakpoint:

Tips: ndb is an out-of-the-box node debugger tool. It is very convenient to debug node applications without any configuration.

OK, the answer is revealed. In the ouput.publicPath = './' scenario, this middleware will be hit, and the serveIndex function will be executed to return the file directory list, which is very sense.

However, as a pursuing programmer, how can you stop here? Let's continue digging down: What piece of code determines whether the process will enter the serveIndex middleware?

Entry point: determine the upstream middleware of serveIndex

Think about it, the characteristic of the express architecture is-based on the onion model of middleware, and the next middleware is next function between the middleware.

Well, there is an idea. We follow the middleware queue of webpack-dev-server and find serveIndex what middleware is available before 06164f338c5b54. Analysis of the code of these middleware should be able to answer:

Which piece of code determines whether the process will enter the serveIndex middleware?

However, under the express middleware architecture, there next call to the actual middleware function. It is difficult to judge the upper-level middleware through the call stack of the breakpoint, and the higher-level middleware is in where:

At this time, you can't be rigid, you have to change a technique-find the code to create the express example, and use magic to wrap the use function:

Tips: This technique is particularly useful in some complex scenarios. For example, when I was learning the Webpack source code, I often used the Proxy class to implant debugger statements on the hook to track who the hook was monitored and where it was triggered.

Through this method of rewriting functions and implanting breakpoints, we can easily trace back which middleware is used by webpack-dev-server and the order of middleware registration:

setupCompressFeature => 注册资源压缩中间件
setupMiddleware => 注册 webpack-dev-middleware 中间件
setupStaticFeature => 注册静态资源服务中间件
setupServeIndexFeature => 注册 serveIndex 中间件

You can see a total of four registered middleware functions in the current configuration Webpack, four middleware will be executed according to the registration order from top to bottom in accordance with the execution logic express, so serveIndex immediately upstream function is setupStaticFeature registered static Resource service middleware.

Continue to look at the code of the setupStaticFeature

Here is just calling the standardized [express.static](https://expressjs.com/en/starter/static-files.html) function to inject the static resource service function. If the corresponding file resource cannot be found according to the path when this middleware is running, the next middleware will be called to continue processing the request, which seems to have nothing to do with our problem. .

Go on and look at the setupMiddleware function:

Registered webpack-dev-middleware , you can see from the name that this middleware webpack-dev-server to do with 06164f338c5cbe, then continue to open webpack-dev-middleware see the code inside:

I go. . . It's not too much, it seems too hard, I just want to find the cause of this bug, there is no need to read it all! Then try to search for the keyword publicPath

Fortunately, publicPath keyword is still relatively small:

  • webpack-dev-middleware/lib/middleware.js is used 1 time in the file
  • webpack-dev-middleware/lib/util.js is used 23 times in the file

Then, first pick the soft persimmon and see how it is used in the middleware.js

const { getFilenameFromUrl } = require('./util');

module.exports = function wrapper(context) {
  return function middleware(req, res, next) {
    function goNext() {
      // ...
      resolve(next());
    }
    // ...
    let filename = getFilenameFromUrl(
      context.options.publicPath,
      context.compiler,
      req.url
    );

    if (filename === false) {
      return goNext();
    }

    return new Promise((resolve) => {
      handleRequest(context, filename, processRequest, req);
      // ...
    });
  };
};

Note that the code in a logical, is to call util file getFilenameFromUrl function, and determine the return filename whether the value of false , is then invoked next function, it looks like that story!

Then go in and take a look at the code of getFilenameFromUrl

Analyze it line by line, and pay attention to the sentence in the red box:

if(xxx && url.indexOf(publicPath) !== 0){
  return false;
}

Reason, from the literal sense of the url should be sent, the client requests a connection, publicPath should be that we webpack.config.js -configured output.publicPath value of the item, right? Let's run it and see:

Sure enough, after entering the breakpoint, you can see that these two values do indeed conform to the previous conjecture. The problem lies here. At this time:

  • url = '/`'
  • publicPath = output.publicPath = '/helloworld'
  • So url.indexOf(publicPath) === false real hammer

getFilenameFromUrl function execution result is false , so webpack-dev-middleware will directly call the next method to enter the next middleware.

If you manually add the content of output.publicPath after the path opened by default:

Sure enough, it worked again.

Step 5: Summary

Hey, you see, this is the process of source code analysis. It is tedious but not complicated. Everyone can become a great technologist. Review the flow of the code:

  • webpack-dev-server started, it will call to automatically open the browser to access the default path http://localhost:9000
  • At this time, webpack-dev-server receives the default path request, and gradually walks along the express logic to the webpack-dev-middleware middleware
  • webpack-dev-middleware middleware, it continues to call the getFilenameFromUrl method of the webpack-dev-middleware/lib/util.js
  • getFilenameFromUrl Internal judgment url.indexOf(publicPath)
  • If getFilenameFromUrl returns false then webpack-dev-middleware directly calls next , and the process enters the next middleware express.static
  • express.static tried to read http://localhost:9000 , and found that the file does not exist, the process continues to the last middleware serveIndex
  • serveIndex returns to the product directory structure interface, which does not meet the developer's expectations

In the final analysis, the problem here:

  1. The introduction on the Webpack official website about output.publicPath only said that it would affect the bundle product path, but did not say that it would affect the index path of the main page. The developer said it was very confuse.
  2. webpack-dev-server started, when the page is automatically opened, the output.publicPath value is not automatically appended to the link. The default opened path is inconsistent with the real index homepage, and it has not returned to 404 and other general error prompts, instead of an unknown file On the list page , it is difficult for developers to quickly get to where the problem is

At this point, I have dug up the problem from the appearance, to the principle, to the most fundamental problem, and I can tell other students in the future:

During the development phase, try to avoid configuring output.publicPath , otherwise there will be surprises\~\~

Truly·Summary

The entire debug process took about half an hour, and the document was written in the middle of the night. . . I thought I would die 1000 words, but I wrote 3000+ in the end. . .

However, the process is indeed used the " how to read the source code - for example to Vetur " processes and techniques mentioned:

  • Clearly define the goal first
  • Review the background and understand the key knowledge points
  • Then define the entry point
  • Analyze the code structure again and guess where the problem may be
  • Then we will analyze it in depth and decipher it layer by layer until the root of the problem.

It can be regarded How to read the source code-taking Vetur as an example ". I hope that readers can think about it, and everyone can do source code analysis.


范文杰
1.4k 声望6.8k 粉丝