This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Full text 3000 words, welcome to like forward

One day, a friend asked me: “Why can’t I see the page I wrote after my Webpack runs?”

Huh? File list page? Well, I don’t think I’ve ever been in a situation where I can’t give you an answer right away, except for the key code:

Focus on the webpack.config.js configuration, using the devServer + HMR function, where:

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

After reading it for a long time, there was no problem. I still couldn’t solve the problem by giving several paper suggestions, so I temporarily put it down at the meeting. After a while, my friend rushed over to tell me that after a blind guess, the problem was solved:

  • output.publicPath = '/'When everything is fine
  • output.publicPath = './'Return to the file list page

Ah? It also affects devServer performance, something tells me I shouldn’t.

Emmm has successfully piqued my curiosity. Although I have written some articles on Webpack source code analysis, webpack-dev-Server is really beyond my knowledge. Fortunately, I have the secret book “How to Read Source code — Vetur as an Example”, and it is time to show the real technology!

Step 1: Define the problem

Let’s review how the problem happened:

  • webpack.config.jsAlso configuredouput.publicPathdevServer
  • runnpx webpack serveStart the development server
  • Browser accesshttp://localhost:9000Instead of returning the user code as expected, the file list page is returned; But if you restoreoutput.publicPathDefault configuration, business as usual

The publicPath of ouput. PublicPath only affects the path referenced by the end product.

Tips: Sometimes you can try to bypass the browser’s complex logic and use the simplest tools to validate what is returned from an HTTP request.

As you can see, requesting the http://localhost:9000 address returns a long list of HTML code with the title listing Directory — that is, the listing page we see:

I don’t know at what level this was generated, but I definitely didn’t write it, and it happened at the HTTP level.

So the core of the question is: why does Webpack output.publicPath affect the performance of webpack-dev-server?

Step 2: Review the background

With the problem, I reviewed the official Webpack document again.

publicPathconfiguration

First 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.

This is an option that controls load on demand or resource file loading, and returns 404 if the corresponding path resource fails to load.

Output. publicPath changes the path of the output resources in the HTML file. For example, after Webpack compilers have generated the bundle.js file, the default path of the output resources in the HTML file is:

<script src="bundle.js" />
Copy the code

If output.publicPath is set, prefixes are added to the path:

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

It looks simple enough.

devServerConfiguration items

Let’s 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.

The devServer configuration is eventually consumed by Webpack-dev-server, which provides web services including HMR — module hot updates.

You can imagine how important webPack-dev-server is when scaffolding tools like VUe-CLI and create-react-app rely on webpack-dev-server at the bottom.

Step 3: Analyze the problem

According to the existing information and my understanding of HTTP protocol, it can be basically inferred that the problem must be caused by the logic of webpack-dev-server framework to process home page requests. It is most likely that outpute. publicPath attribute affects the judgment logic of home page resources. As a result, webpack-dev-server cannot find the corresponding resource file and returns to the file list page at the bottom of the pocket.

Well, I feel reliable, then along this train of thought to dig a dig source, find specific reasons.

Step 4: Analyze the code

Structure analysis

Open the webpack-dev-server package to see the contents of the code:

The Clone WebPack-dev-server repository can also be used with clone webPack-dev-server

The structure of the project is not complicated, and we can infer that the main code is in the lib directory according to the custom of Webpack:

Cloc is a very useful code statistical tools, website: www.npmjs.com/package/clo…

The code is just over 2000, which is fine.

Next, open the package.json file and see what dependencies there are. After listing one by one, the dependencies that are strongly related to our problem are:

  • express: Apps need no more introduction
  • webpack-dev-middlewareThis is a middleware that Bridges the Webpack compilation process with Express
  • serve-index:Express middleware that provides a file list page under a specific directory!!!!!!!!!

According to this description, it must be in the serve-index call ah, feel very close to the answer.

Local analysis

Pointcuts: Validationserve-indexThe role of package

Webpack-dev-server uses the serve-index package as a reference to the webpack-dev-server package.

Fortunately, this is only used in lib/ server.js files, so it is much easier to statically analyze the statements before and after the call statement, which can be roughly derived:

  • serveIndexThe call is wrapped inthis.app.useInside, presumably,this.appPointing to the Express example,useFunction is used to register middleware, so the wholeserveIndexIt’s middleware
  • In addition tosetupStaticServeIndexFeatureOutside,ServerThe type also contains other names calledsetupXXXFeatureAre basically used to add Express middleware, which combines HMR, proxy, SSL and other functions provided by Webpack-dev-server

Also can’t see what else, first do a control experiment, run the dynamic analysis of the actual code execution process, verify whether this place is wrong. Insert the debugger statement before the serveIndex function, then:

  • Let’s do what’s normal, which isoutput.publicPath = '/'performndb npx webpack serve, the result is that the page opens as usual, with no breakpoints hit and no interruptions
  • Then according to theouput.publicPath = './'performndb npx webpack serve, enter the breakpoint:

Tips: NDB is an out-of-the-box Node debugger tool that allows you to debug node applications without doing any configuration

PublicPath = ‘./’ hits this middleware, executes serveIndex to return a list of file directories, which makes sense.

However, as an aspiring programmer, how did you end up there? Let’s dig deeper: Which piece of code determines whether or not a process ends up in serveIndex middleware?

Entry point: ConfirmserveIndexUpstream middleware

If you think about it, the Express architecture is characterized by an onion model based on middleware, with middleware calling up the next middleware through the next function.

The middleware queue of Webpack-dev-server can be used to find out what middleware exists before serveIndex.

Which piece of code determines whether or not a process enters the serveIndex middleware?

However, with Express middleware architecture, there is a long call link between the next call and the actual middleware function, so it is difficult to determine where the upper level of middleware is based on the broken call stack:

Instead of being rigid, try a different trick — find the code that created the Express example and put magic around the use function:

Tips: This technique is especially useful in some complex scenarios. For example, when I was learning Webpack source code, I often used the Proxy class to implant debugger statements into hooks, tracking who is listening to hook, and where is triggered

By rewriting the function and embedding breakpoints, we can easily trace which middleware is used by Webpack-dev-server and the order in which middleware is registered:

SetupCompressFeature => Register resource compression middleware setupMiddleware => register Webpack-dev-middleware setupStaticFeature => Register static resource service middleware SetupServeIndexFeature => Register serveIndex middlewareCopy the code

As you can see, all four middleware functions are registered under the current Webpack configuration, and the four middleware functions are executed from the top down in the order of registration according to express execution logic. Therefore, the direct upstream of the serveIndex function is the static resource service middleware registered by the setupStaticFeature.

Continue to look at the code for the setupStaticFeature function:

Here just call standardized [express static] (https://expressjs.com/en/starter/static-files.html) function, into a static resource service function, if the time of this middleware run according to the path to find the corresponding file resources, The next middleware will be called to continue processing the request, which seems irrelevant to our problem.

Further up, look at the setupMiddleware function:

Register for Webpack-dev-middleware. If webpack-dev-server is not used, open webpack-dev-middleware and check out the code:

I’m going to… It seems like a lot of work. I just want to find out the cause of this bug. There is no need to read it all! Try searching for publicPath:

Fortunately, the publicPath keyword is relatively infrequent:

  • webpack-dev-middleware/lib/middleware.jsWas used once in the file
  • webpack-dev-middleware/lib/util.jsIt was used 23 times in the file

Take a look at the middleware.js file to see how it works:

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);
      // ...
    });
  };
};
Copy the code

Note that there is logic in the code to call getFilenameFromUrl and determine whether filename returned is false, if so, call next. This looks like it!

Go ahead and look at the code for getFilenameFromUrl:

Break it down line by line, and notice the sentence in the red box:

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

PublicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath = output.publicPath Run it and see:

When the breakpoint goes in, you can see that these two values are exactly the same as the previous guess.

  • url = '/`'
  • publicPath = output.publicPath = '/helloworld'
  • sourl.indexOf(publicPath) === falseCompaction hammer

The getFilenameFromUrl function returns false, so Webpack-dev-Middleware calls next directly to the next middleware.

If you manually add output.publicPath to the default open path:

Sure enough, it worked again.

Step 5: Summarize

Look, this is the process of source code analysis. It’s tedious but not complicated. Review the flow of the code:

  • webpack-dev-serverWhen started, the automatic open browser is called to access the default pathhttp://localhost:9000
  • At this timewebpack-dev-serverThe default path request is received, and the express logic steps up towebpack-dev-middlewaremiddleware
  • webpack-dev-middlewareAnd inside the middleware, it calls againwebpack-dev-middleware/lib/util.jsOf the filegetFilenameFromUrlmethods
  • getFilenameFromUrlInternal judgmenturl.indexOf(publicPath)
  • ifgetFilenameFromUrlreturnfalsewebpack-dev-middlewareDirect callnext, the process goes to the next middlewareexpress.static
  • express.staticTry to readhttp://localhost:9000The corresponding resource file is found not to exist, and the process continues to the last middlewareserveIndex
  • serveIndexReturns the product directory structure interface, not as expected by the developer

Ultimately, the question is:

  1. Output. publicPath can affect the bundle product path, but does not affect the index path of the main page
  2. webpack-dev-serverWhen started, autoopen pages do not append automatically after linksoutput.publicPathValue causes the default open path to be inconsistent with the real index home page and has not yet returned404A generic error message, replaced by an unknown oneFile list pageIt’s hard to quickly figure out what the problem is

Here I have dug out the problem from the appearance, to the principle, to the most fundamental problem.

During development, try to avoid configuring output.publicPath, otherwise there will be a surprise oh ~~

True, summary

The whole Debbug process took about half an hour and the documentation was written in the middle of the night… Originally thought to support dead 1000 words, finally wrote 3000+…

However, the processes and techniques described in How to Read source code — Vetur as an Example are used:

  • Start by clearly defining your goals
  • Review the background for key points
  • Define the pointcut again
  • Look again at the structure of the code and guess where the problem might be
  • Again and again local in-depth analysis, layer by layer decryption until the root of the problem

As a supplement to “How to read source code – with Vetur as an example”, I hope readers can think, learn, and everyone can do source analysis.