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

Assets url prefix support #17

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

istickz
Copy link

@istickz istickz commented Nov 29, 2024

Description of the Problem

When working with multiple frontend applications, such as app and admin, each with its own Vite configuration, we can create multiple Vite fragment tags, like so:

func ViteAdminTags() template.HTML {
    var err error
    assetsApp, _ := fs.Sub(app.DistFS, "admin")
    viteConfig := vite.Config{
        IsDev:        app.InDevelopment,
        ViteURL:      "http://localhost:5174", // Development URL for 'admin'
        ViteEntry:    "src/js/main.js",
        ViteTemplate: vite.Vanilla,
        FS:           assetsApp,
    }

    viteFragment, err := vite.HTMLFragment(viteConfig)
    if err != nil {
        log.Printf("Vite fragment error: %v", err)
        return "<!-- Vite fragment error -->"
    }

    return viteFragment.Tags
}

func ViteTags() template.HTML {
    var err error
    assetsApp, _ := fs.Sub(app.DistFS, "app")
    viteConfig := vite.Config{
        IsDev:        app.InDevelopment,
        ViteURL:      "http://localhost:5173", // Development URL for 'app'
        ViteEntry:    "src/js/main.js",
        ViteTemplate: vite.Vanilla,
        FS:           assetsApp,
    }

    viteFragment, err := vite.HTMLFragment(viteConfig)
    if err != nil {
        log.Printf("Vite fragment error: %v", err)
        return "<!-- Vite fragment error -->"
    }

    return viteFragment.Tags
}

This works perfectly in development mode because assets are served with distinct URLs like:

  • http://localhost:5173/ (for app)
  • http://localhost:5174/ (for admin)

However, in production mode, this approach fails because asset URLs default to /assets/*. This results in a collision when serving multiple applications.


Proposed Solution

This PR introduces an AssetsURLPrefix option to the vite.Config struct for production mode. This allows asset links to be generated with a custom prefix, avoiding collisions.

Here’s how it works:

Updated Vite Configurations

func ViteAdminTags() template.HTML {
    var err error
    assetsApp, _ := fs.Sub(app.DistFS, "admin")
    viteConfig := vite.Config{
        IsDev:           app.InDevelopment,
        ViteURL:         "http://localhost:5174", // Development URL for 'admin'
        ViteEntry:       "src/js/main.js",
        ViteTemplate:    vite.Vanilla,
        FS:              assetsApp,
        AssetsURLPrefix: "/admin", // Custom prefix
    }

    viteFragment, err := vite.HTMLFragment(viteConfig)
    if err != nil {
        log.Printf("Vite fragment error: %v", err)
        return "<!-- Vite fragment error -->"
    }

    return viteFragment.Tags
}

func ViteTags() template.HTML {
    var err error
    assetsApp, _ := fs.Sub(app.DistFS, "app")
    viteConfig := vite.Config{
        IsDev:           app.InDevelopment,
        ViteURL:         "http://localhost:5173", // Development URL for 'app'
        ViteEntry:       "src/js/main.js",
        ViteTemplate:    vite.Vanilla,
        FS:              assetsApp,
        AssetsURLPrefix: "", // Default prefix
    }

    viteFragment, err := vite.HTMLFragment(viteConfig)
    if err != nil {
        log.Printf("Vite fragment error: %v", err)
        return "<!-- Vite fragment error -->"
    }

    return viteFragment.Tags
}

Correctly Serving Assets in Handlers

To support this configuration, the assets should be served with the appropriate prefixes in the HTTP handlers:

// Serve 'admin' assets
assetsAdminApp, _ := fs.Sub(app.DistFS, "admin")
assetsAdminAppFS := http.FileServerFS(assetsAdminApp)
mux.Handle("/admin/assets/*", http.StripPrefix("/admin", assetsAdminAppFS))

// Serve 'app' assets
assetsApp, _ := fs.Sub(app.DistFS, "app")
assetsAppFS := http.FileServerFS(assetsApp)
mux.Handle("/assets/*", assetsAppFS)

Benefits

  1. Flexibility: Enables distinct asset URLs for different frontend applications in production mode.
  2. Scalability: Allows seamless integration of multiple frontend applications without asset collisions.
  3. Backward Compatibility: Default behavior remains unchanged unless the AssetsURLPrefix option is explicitly set.

@olivere
Copy link
Owner

olivere commented Dec 2, 2024

Thank you very much for the PR. I think this is really useful.

I'm still looking to understand how this all plays together with Vite's base configuration setting. What I'm think of is: Maybe the base URL is already stored in the Vite manifest and we could pick it up from there, so you don't have to configure anything? Or: What happens if you set a base in Vite and use a different assetsURLPrefix in our config here: Can we somehow find out?

Would also be lovely to have a working example in /examples/multiple-vite-apps or something, maybe by starting with the /examples/router and adding another Vite app with /admin as base URL.

@danclaytondev
Copy link
Contributor

Hi @istickz, thanks for the great PR. @olivere my initial reaction was the same as yours, how does this interact with the base config option. I just did some testing and adding a base doesn't change what is generated in the manifest at all. This is backed up by this. The base will have some impacts in image imports etc, I think it modifies URLs inside code (js files etc) but doesn't change what is in the manifest, hence we can't support istickz's use case. It is something we need to handle at our level.
I'm not sure what happens if we do use a different assetsURLPrefix to the base, that might break something but we could let the user do that and it's up to them.

I just had a look at how Laravel supports this (with it's built in Vite code that does the same as this package). In Laravel you can provide a path to the build directory and it uses that to build the tag URL, although that is more specific to how laravel serves the files.
You can also provide a callback which allows you to rewrite the generated tags/URLs to Vite assets.

So you can do things like this:

use Illuminate\Support\Facades\Vite;

Vite::createAssetPathsUsing(function ($path, $secure = null) {
    // Custom URL generation logic here
    return 'https://custom-domain.com/' . ltrim($path, '/');
});

I think there is a good use case for @istickz's PR, or we could implement a callback for greater flexibility.

@olivere
Copy link
Owner

olivere commented Dec 2, 2024

Yes, I want this PR to be merged as well. It is really valuable.

@danclaytondev I'm very happy looking at what the libraries for other platforms/languages have done, as they exist quite a bit longer and hence have more experience with the right approach. So I would be happy with the AssetsPrefixURL setting and an (optional) AssetsPrefixURLCallback (in lack of better naming).

As to possible misconfigurations: I always love when an error gives me hints on how to fix an error. Something like "Make sure the base URL in the Vite config is equal to the AssetsPrefixURL" would already make me happy. I spend so much time in my career fixing the same issue over and over again (but couldn't quite remember how to solve it). A simple hint would've really helped me. Well, maybe it's just me.

So, if we can have another example application that would be 👍 I can work on it, but it might be Friday before I can finish it.

@istickz
Copy link
Author

istickz commented Dec 6, 2024

The base option from Vite is generally not necessary if we're only changing the prefix for where we fetch assets. However, it becomes relevant when rendering HTML from a different URL, for example:
/admin -> serves an HTML page where the assets are embedded.

If our application has a router or uses import.meta.env.BASE_URL, we also need to specify the base option as /admin when building the frontend application.

If we're just setting assetPrefixURL and rendering the page at /, there's no need to modify the base option.

@istickz
Copy link
Author

istickz commented Dec 6, 2024

I have added examples to provide better understanding of how everything works.

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

Successfully merging this pull request may close these issues.

3 participants