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

dynamic publicPath support #3522

Closed
dongnaebi opened this issue May 24, 2021 · 25 comments · Fixed by #8450
Closed

dynamic publicPath support #3522

dongnaebi opened this issue May 24, 2021 · 25 comments · Fixed by #8450
Labels
enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority) p2-to-be-discussed Enhancement under consideration (priority)

Comments

@dongnaebi
Copy link

Clear and concise description of the problem

I want to inject a dynamic publicPath(base in vite config),lick this:

export default defineConfig({
    base: `window.baseFromServer`
})

Suggested solution

provide some build hook, just like webpack's hook, in this hook I can write a plugin to replace some string, reference:

https://github.com/m8ms/dynamic-public-path-webpack-plugin

Alternative

or support it in core

Additional context

none

@sh-winter
Copy link

sh-winter commented May 24, 2021

This is also very important for me, and I hope to use something like __webpack_public_path__ same function

@dongnaebi
Copy link
Author

dongnaebi commented May 24, 2021

I think we have to consider both of compile-time and runtime

in compile-time, I want to inject a variable to dist/index.html, that I can replace it in server render, like this:

<!--dist/index.html-->
<div id="app"></div>
<script src="{{dynamicPublicPath}}/app.js"></script>

it's very useful for multi-tenant system

in runtime, just like __webpack_public_path__

so, I think the base api maybe like this:

export default defineConfig({
  // base: '/',
  base: {
    compileTime: '{{dynamicPublicPath}}',
    runtime: 'window.dynamicPublicPath' // string way: `'https://img.xxx.com'`
  }
})

@luoyang125024608
Copy link

luoyang125024608 commented May 25, 2021

I also need such program capabilities at runtime,same as __webpack_base_uri__ in webpack5

@alizeait
Copy link

Agree, something like webpack_public_path is really necessary to have the ability to build once and deploy on different domains where relative paths isn't an option

@wjzheng

This comment was marked as abuse.

@dongnaebi
Copy link
Author

You can try to use the html base attribute ,eg: <base url="https://www.domain.com/" />

It works on all relative paths

@alizeait
Copy link

That wouldn't work for dynamic imports right?

@jy0529
Copy link

jy0529 commented Jul 1, 2021

You can create a vite plugin to do this.
image

Declare a global function named dynamicImportHanlder, transform the dynamic import path to other string.

@dongnaebi
Copy link
Author

I can use rollup-plugin-replace too, but the point is I can't replace the preload's link, because all plugins is run before buildImportAnalysisPlugin

see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L284

of course it can be realized with node's fs API, emmm...

@jy0529
Copy link

jy0529 commented Jul 6, 2021

I created a plugin to do this, support preload's link.
https://github.com/jy0529/vite-plugin-dynamic-publicpath

// vite.config.ts

import { useDynamicPublicPath } from 'vite-plugin-dynamic-publicpath'
export default defineConfig({
  plugins: [
    useDynamicPublicPath(/** option */),
  ]
})
// main.ts

// Your dynamic cdn
const dynamicCdn = oneOf(['cdn.xxx.com', 'cdn1.xxx.com']);
window.__dynamicImportHandler__ = function(importer) {
    return dynamicCdn + importer;
}
window.__dynamicImportPreload__ = function(preloads) {
    return preloads.map(preload => dynamicCdn + preload);
}

@fcloud89
Copy link

Any suggestion for this feature?
@patak-js @yyx990803

@davidmerrique
Copy link

I'd also like this feature to be builtin.

I work with Salesforce Commerce Cloud, and we don't have control over our static URL path, it looks something like this https://www.something.com/on/demandware.static/Sites-ID-Site/-/en_US/v1637597419042/js/app.js

The <base/> tag works, but if I set it to https://www.something.com/on/demandware.static/Sites-ID-Site/-/en_US/v1637597419042/js/ it breaks all my relative anchor links (like <a href="#some-id">)

@imsobear
Copy link

+1 for this feature

@davidwallacejackson
Copy link
Contributor

this would be really useful for us as well!

@bluwy
Copy link
Member

bluwy commented Mar 9, 2022

Is this the same as base: "./"?

@StephaneRob
Copy link

StephaneRob commented Mar 15, 2022

+1 for this feature. It will be really helpful to generate agnostic builds.

@P1X3L

This comment was marked as spam.

@bluwy bluwy mentioned this issue Apr 1, 2022
4 tasks
@antfu antfu added enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority) p2-to-be-discussed Enhancement under consideration (priority) and removed enhancement: pending triage labels Apr 1, 2022
@danielroe
Copy link
Contributor

Is this the same as base: "./"?

There are a couple of issues with this.

  1. Imagine you wish to host your assetDir on a CDN. Changing the base to ./ won't do what we want; it will resolve everything relative to the domain where your site is hosted.
  2. Imagine you want to update your base at runtime. The current approach still hard-codes a path throughout the app.
  3. Assets in HTML will point at the wrong location. Consider the following HTML:
    <div>
      <img src="./assets/logo.148d9522.svg" class="h-20 mb-4">
    </div>
    This will be fine in / but not in /nested/page.

@patak-dev
Copy link
Member

patak-dev commented Apr 6, 2022

base: './' also currently has issues with preloading. See #2009. It works when the index.html is in the root, but not for nested HTML. We can fix it by using import.meta.url as proposed in #2009. Looks like to be able to do so, we will need to bump our current modern targets to support import.meta.url (it would go from browsers supporting dynamic import 91.47% to 91.22%, so I think it is doable in Vite 3).

  1. Imagine you wish to host your assetDir on a CDN. Changing the base to ./ won't do what we want; it will resolve everything relative to the domain where your site is hosted.

I wonder if the best would be to use the base only for the .html, all URLs in assets should be relative between themselves so the assets can be portable (#7611 (comment))

  1. Imagine you want to update your base at runtime. The current approach still hard-codes a path throughout the app.

@danielroe How does this work for the example that you showed:

<div>
  <img src="./assets/logo.148d9522.svg" class="h-20 mb-4">
</div>

How do you update the base at runtime? Do you crawl the HTML to update all the assets src and href?

If this is unknown at build time, but fixed at the server, I imagine the HTML also needs to be transformed in the same way during SSR.

@danielroe
Copy link
Contributor

danielroe commented Apr 6, 2022

@patak-dev We're using SSR and our vite plugin currently generates code approximating this:

const logo = buildAssetsURL("logo.148d9522.svg");
const _imports_1 = publicAssetsURL("public.svg");
// ...
const _sfc_main = {
  setup(__props) {
    return (_ctx, _push, _parent, _attrs) => {
      _push(`
        <img${serverRenderer.exports.ssrRenderAttr("src", _imports_1)}>
        <img${serverRenderer.exports.ssrRenderAttr("src", unref(logo))}>
      `);
    };
  }
};

buildAssetsURL and publicAssetsURL return the currently correct values of these paths.

@patak-dev
Copy link
Member

buildAssetsURL and publicAssetsURL depend only on server state, no? They can't change on the client afterward.

What kind of API do you imagine Vite would expose for this feature?

Just a note, but I think that base: './' could end up working like Webpack's publicPath: 'auto'

@danielroe
Copy link
Contributor

The client-side state does need to match the HTML from the server, so that it continues to fetch assets from the correct locations as users navigate through the site. Once the JS is loaded, I am not imagining it will change or need to be reactive.

Here's a stab at an API:

  • emitted assets should use relative URLs where possible, as we can know this structure absolutely, as you say above. this is a relatively easy solution and can be implemented as a first step.

  • ideally we should distinguish the built assets directory from the public directory as one containes hashed assets and the other doesn't - and performance-conscious users might like to have different caching rules for public assets vs built assets

  • therefore it should be possible to configure (once) a publicAssetsURL + buildAssetsURL (ideally functions that would take a string and return a url?). All asset strings that would be produced in the build process can simply use these values in a template literal - e.g.:

    // instead of
    const logo = './logo.svg'
    const someHTML = '<img src="./test.234k.svg">'
    // we generate
    const logo = publicAssetsURL('logo.svg')
    const someHTML = `<img src="${buildAssetsURL('test.234k.svg')}">`

    We might allow users to provide their own publicAssetsURL as follows:

    const publicAssetsURL = window.__vite_public_assets_url || (id) => '/' + id
    const buildAssetsURL = window.__vite_build_assets_url || (id) => '/assets/' + id

    Updating the URLs would be as simple as injecting a script tag into the HTML that sets those global functions and allows users to have full control depending on their own setups.

@patak-dev
Copy link
Member

@danielroe once we merge #7644, we are going to end up with:

// we generate

const logo = new URL('../logo.svg', import.meta.url)
const someHTML = '<img src="' + new URL("../assets/test.234k.svg", import.meta.url) + '">'

(changed the interpolation to showcase more closely the output that Vite would generate, but it is the same as in your prev message)

The URL needs to be absolute because the logo variable could be used anywhere, that is why even for public assets new URL(..., import.meta.url) is used

Getting back to your proposed API for dynamic publicPath support, we now have the infra to add the feature. It would mean changing the way we deal with generated public paths in JS here: https://github.com/vitejs/vite/pull/7644/files#diff-91776e7c6039d23a070162f02a69cd46046a2095bd5ecb384ae9e27f2ea5288fR123. So we could support something like you proposed or an equivalent configuration:

const publicAssetsURL = window.__vite_public_assets_url || (id) => '/' + id

Two questions:

  1. For the buildAssetsURL, could it be that it isn't needed? The JS files would always be served from the same path as the assets, no? And in that case, the relative base is enough.

  2. For public asset files in CSS asset files, how are you dealing with them? With feat!: relative base #7644 they will be relative to the CSS file they are in. But we can't interpolate with CSS vars in url(). We could do something like:

:root {
  --public-url-12a3: url("https://some.cdn.com/logo.svg");
}
body {
  background: var(--public-url-12a3);
}

And then update these vars in an inlined script module in the HTML? Or are you not planning to use public assets from CSS at all?

@patak-dev
Copy link
Member

There is initial work to support this feature in:

Reviews and feedback from the folks involved in this issue are appreciated, especially around the API design and coverage for the use cases exposed here.

@jasondmichaelson
Copy link

I'm just doing some playing around with this feature, and it still seems like these dynamic paths are static once set at build time. I was kind of expecting something that could be configured at runtime but that doesn't seem to be the case. Am I wrong?

@github-actions github-actions bot locked and limited conversation to collaborators Jul 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority) p2-to-be-discussed Enhancement under consideration (priority)
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.