vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
67.95k stars 6.12k forks source link

Support base url with port for scripts in index.html #14263

Open Richard-Walton opened 1 year ago

Richard-Walton commented 1 year ago

Description

I have a slightly unusual dev environment where I run multiple versions of a vite application behind a load balancer.

To access the application I goto my dev host: e.g. http://mydev?version=1 or http://mydev?version=2 The load balancer then directs traffic to appropriate application running on themydev host (A locally running VM)

A request to load the app is therefore handled as follows:

http://mydev?version=1 -> load-balancer:80 -> node-backend-server:1000 -> vite-frontend-server:1111

or

http://mydev?version=2-> load-balancer:80 -> node-backend-server:2000 -> vite-frontend-server:2222

The vite server responds with HTML which, no matter now hard I try, contains relative urls. These relative urls result in 404s as the node-backend-server cannot handle the requests.

An example HTML generated by vite (with comments explaining what I would like to happen)) looks like:

<!doctype html>
<html lang="en">
  <head>
    <script type="module" src="/@vite/client"></script> <!-- THIS  IS THE PROBLEM -->

    <!-- I WOULD LIKE VITE TO GENERATE THIS -->
    <script type="module" src="http://mydev:<FRONTEND_PORT>/@vite/client"></script> 

  </head>
  <body>
    <script type="module" src="/src/main.tsx"></script> <!-- SAME ISSUE -->
        <!-- I WOULD LIKE VITE TO GENERATE THIS -->
    <script type="module" src="http://mydev:<FRONTEND_PORT>/src/main.tsx"></script> 
  </body>
</html> 

I have tried setting base to http://mydev:<frontend-port> but that didn't work. I have tried setting base to :<frontend-port> but that didn't work. I have tried using the experimental renderBuiltUrl but that didn't work.

Before using vite, we used webpack and were able to get things working using their publicPath option (Setting it to http://mydev:<frontendport>

Suggested solution

Script tags in index.html should be configurable using an absolute base url

Alternative

No response

Additional context

No response

Validations

bluwy commented 1 year ago

Yeah Vite currently doesn't handle this case too well in dev. The right configuration would be base and that should work in build, however in dev, we always truncate http://something/ to / to simplify loading things in dev. The expectation was that a full URL base is mainly for the prod URL only.

This would be hard to support though and requires refactoring many parts of the codebase.

Richard-Walton commented 1 year ago

@bluwy Crumbs! Are you able to advise of any workaround I could implement? Some kind of post processing html plugin? Failing that I can rewrite the responses in my load balancer but I'd prefer a vite centric solution if possible.

Many thanks

bluwy commented 1 year ago

You can write a Vite plugin and use the transformIndexHtml hook to prepend the host, something like:

const plugin = {
  name: '',
  transformIndexHtml: {
    order: 'post', // after vite modifies the links
    handler(html) {
      // parse and replace the links
      return html
    }
  }
}

I'm not sure if there's more to this though, presumably the existing scripts should work without the explicit hostname as they're already fetched relatively to the hostname they're loaded from.

arthow4n commented 6 months ago

I wrote a plugin inspired by the comment above and it worked well, there are some changes more than the transform though. I use Vite dev server with middleware mode so the @vite/client handling part might be different.

export const vitePluginRewriteBase = (viteRealBase: string): Plugin => {
    const viteFakeBaseForVitePluginRewriteBase = "/VITE_PLUGIN_REWRITE_BASE_PATH";
    const viteFakeBaseRegex = new RegExp(viteFakeBaseForVitePluginRewriteBase, "g");

    return {
        name: "vite-plugin-rewrite-base",
        apply: "serve",
        config(config) {
            config.base = viteFakeBaseForVitePluginRewriteBase;
        },
        transform: {
            order: "post",
            handler(code, id) {
                // Handle `import "@vite/client"` which is the HMR client
                if (id.endsWith("node_modules/vite/dist/client/client.mjs")) {
                    // Don't rewrite the base here, because Vite HMR's middleware listens on ws://.../<vite config's base>
                    return code;
                }

                // Rewrite all other bases in the code to make sure they point to Vite dev server's domain.
                // This is mainly fixing URL references in CSS to make them point to the Vite dev server,
                // because CSS code will be injected to the main page on load,
                // making the base point to the backend server (=where the main page is served) instead of the Vite dev server if we don't rewrite them,
                // so here we write to make sure those relative paths become absolute path with the Vite dev server's domain.
                return code.replace(viteFakeBaseRegex, viteRealBase);
            },
        },
        transformIndexHtml: {
            order: "post",
            handler(html) {
                // Rewrite to make sure the assets are pointed to Vite dev server.
                // Because the HTML is served by the backend, relative paths are pointed to the backend instead of the Vite dev server.
                // Therefore we rewrite the relative path to absolute path with the Vite dev server's domain here.
                return html.replace(viteFakeBaseRegex, viteRealBase);
            },
        },
    };
};

then in the config:

import { vitePluginRewriteBase } from "./vitePluginRewriteBase";

defineConfig(({ command }) => ({
    plugins: [
        vitePluginRewriteBase(viteDevServerUrl),
    ],
}))