Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need help with migrating a custom middleware from v3 to v5 of webpack-dev-server #18360

Closed
Surya97 opened this issue Apr 26, 2024 Discussed in #18358 · 1 comment
Closed

Comments

@Surya97
Copy link

Surya97 commented Apr 26, 2024

Discussed in #18358

Originally posted by Surya97 April 26, 2024
I am trying to bump my webpack-dev-server from v3 to v5 and I am facing some issues converting my custom middleware. In v3 I am using the before option which was making the middleware work fine but now with the setupMiddlewares option, the middleware is not working as expected.

Here is my function which I have updated to work with the new changes.

import cors from 'cors';
import { ensureLeadingSlash, stripLeadingSlash } from './utils';

type ReplacementValues = Record<string, string>;
const MANIFEST_FILE_NAME = 'manifest.json';

interface ManifestReplacement {
  /** The string to replace in a file. */
  template: string;
  /** The replacement string that will be used in development. */
  developmentValue?: string;
  /** The replacement string. */
  replacement: {
    key: string;
  };
}


export function createMiddleware(
  _options: any = {},
  server: any,
  req: any,
  res: any,
) {
    server.app.use(cors());
    server.compiler.hooks.done.tap('customMiddleware', (stats) => {
      const assets = stats.compilation.assets;
      const host = req.headers.host;
      const isHttps = req.protocol === 'https';

      const manifest = JSON.parse(getFile(MANIFEST_FILE_NAME));

      const path = stripLeadingSlash(req.path);

      if (path === MANIFEST_FILE_NAME) {
        res.send(getHtml(`<pre>${JSON.stringify(manifest, null, 2)}</pre>`));
        return;
      }

      const asset = findFile(path);

      if (!asset) {
        return sendError(
          `<p>
              Requested path "${path}" does not exist in ${MANIFEST_FILE_NAME}.
              Try one of the following:
            </p>
            <ul>
              ${manifest.files
                .map((f) => {
                  const l = f.destinationLocation;
                  return `<li><a href="/${l}">${l}</a></li>`;
                })
                .join('')}
            </ul>`,
        );
      }

      const replacements: ReplacementValues = {
        CloudFrontDomain: host,
        CloudFrontProtocol: isHttps ? 'https' : 'http',
      };

      const rawSource = getFile(asset.sourceLocation);
      const headers = processHeaders(asset.headers, replacements);
      const source = doReplacements(
        rawSource.toString(),
        asset.replacements,
        replacements,
      );

      // disable caching in development
      headers['cache-control'] = 'no-store';

      res.set(headers).send(source);

      function getFile(name: string) {
        name = ensureLeadingSlash(name);
        const fname =
          server.middleware.getFilenameFromUrl(name) ??
          server.compiler.options.output.path + name;
        return server.compiler.outputFileSystem.readFileSync(fname).toString();
      }

      function findFile(name: string) {
        return manifest.files.find((f) => f.destinationLocation === name);
      }

      function processHeaders(
        headers: Record<string, string>,
        replacements: Record<string, string>,
      ) {
        const newHeaders = {};

        for (let [header, value] of Object.entries(headers)) {
          if (typeof value !== 'string') {
            value = processTemplate(value.template, replacements);
          }
          newHeaders[header] = value;
        }

        return newHeaders;
      }

      function processTemplate(template, replacements) {
        return Object.entries(replacements).reduce(
          (t, [key, value]) => t.split(`{{${key}}}`).join(value),
          template,
        );
      }

      function doReplacements(
        data: string,
        replacements: ManifestReplacement[],
        values: ReplacementValues,
      ) {
        for (const r of replacements) {
          const val =
            r.developmentValue ??
            values[r.replacement.key] ??
            r.replacement.key;
          data = data.split(r.template).join(val);
        }
        return data;
      }

      function sendError(msg: string) {
        res.status(500).send(getHtml(msg));
      }

      function getHtml(body) {
        return `<!DOCTYPE html><html><body>${body}</body></html>`;
      }
    }
};

And in the devServer configuration, I have this:

setupMiddlewares: (middlewares, devServer) => {
          middlewares.unshift({
            name: 'customMiddleware',
            path: "*",
            middleware: (req, res, next) => {
              console.log("Executed custom middleware");
              createMiddleware({}, devServer, req, res);
            }
          })
          return middlewares;

Now the issues I am facing are:

  • The '/' path is giving error 'Requested path "" is does not exist in manifest.json. Try one of the following:
  • When I am trying to load a route, it is taking forever. Eg: http://localhost:3000/posts/uuid this is not even providing anything. It just goes into infinite loading state.
  • If I am using push instead of unshift, the customMiddleware is not even getting executed as there are other middlewares already sending the response and unable to have unit tests to see if my middleware is working as expected as the response is already being sent by some of the already available middlewares.
  • I believe that if I am doing unshift, I need to handle all the middlewares like connect-history-api-fallback, serve-index etc manually in this custom middleware. If this is true, can you please let me know what all are the required middlewares and how to add that to my custom middleware?

Also, I am seeing duplicate default middlewares when configuring my server. Don't know if that is an issue on configuration side.
image

@Surya97
Copy link
Author

Surya97 commented May 1, 2024

Fixed it by modifying the setupMiddlewares to this:

setupMiddlewares: (middlewares, devServer) => {
         devServer.app.use(historyApiFallback());
          devServer.app.use((req, res, next) => {
            createMiddleware(devServer)(req, res, next);
          });
          // Setup to run proxy middleware after our custom middleware
          middlewares.forEach((middleware) => {
            if (middleware['name'].includes('http-proxy')) {
              devServer.app.use(middleware['middleware']);
            }
          });
          return middlewares;
}

@Surya97 Surya97 closed this as completed May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant