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

The public path setting with history mode #4687

Closed
muei opened this issue Jul 18, 2019 · 12 comments
Closed

The public path setting with history mode #4687

muei opened this issue Jul 18, 2019 · 12 comments

Comments

@muei
Copy link

muei commented Jul 18, 2019

Operating System - Linux(5.0.0-21-generic) - linux/x64
NodeJs - 12.6.0

Global packages
  NPM - 6.10.1
  yarn - 1.16.0
  @quasar/cli - 1.0.0
  cordova - Not installed

Important local packages
  quasar - 1.0.5 -- Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
  @quasar/app - 1.0.4 -- Quasar Framework local CLI
  @quasar/extras - 1.2.0 -- Quasar Framework fonts, icons and animations
  vue - 2.6.10 -- Reactive, component-oriented view layer for modern web interfaces.
  vue-router - 3.0.7 -- Official router for Vue.js 2
  vuex - 3.1.1 -- state management for Vue.js
  electron - Not installed
  electron-packager - Not installed
  electron-builder - Not installed
  @babel/core - 7.5.5 -- Babel compiler core.
  webpack - 4.35.3 -- Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
  webpack-dev-server - 3.7.2 -- Serves a webpack app. Updates the browser on changes.
  workbox-webpack-plugin - 4.3.1 -- A plugin for your Webpack build process, helping you generate a manifest of local files that workbox-sw should precache.
  register-service-worker - 1.6.2 -- Script for registering service worker, with hooks

Quasar App Extensions
  @quasar/quasar-app-extension-dotenv - 1.0.0-beta.10 -- Load .env variables into your quasar project

This issue is related to #4460 , #4451 .
My frontend structure like this:

  1. quasarexample.com is the root host.
  2. Below this host, there are not one frontend project. For example,
  • quasarexample.com/site1
  • quasarexample.com/site2
  • quasarexample.com/about
  1. site1, site2 are stand alone project, and written by quasar. And proxy by nginx or aws cloudfront.
  2. All static files like css, js, images..., are stored in cloud, and CDN like https://cdn.quasarexample.com/js/xsdfdfsdfs.js.
  3. The frontend projects are deployed by docker, and docker image just contain index.html and some config files which can be mount by k8s file.
    So that's why I set the publicPath as host in quasar.conf.js. But this can cause the history mode error, when switch page under /posts/:id like router:
Uncaught DOMException: Failed to execute 'replaceState' on 'History': A history state object with URL 'https://cdn.quasarexample.com/site1/posts/xxxxxxxxxxxx' cannot be created in a document with origin 'https://quasarexample.com' and URL

The url https://cdn.quasarexample.com/site1/posts/xxxxxxxxxxxx is wrong, right is https://quasarexample.com/site1/posts/xxxxxxxxxxxx
I find the problem is base value in head, it built as <base href=cdn.quasarexample.com/>
So can support a confing item to set imported host in head, or some advise, thank you!

@pdanpdan
Copy link
Collaborator

pdanpdan commented Jan 22, 2021

cross reference:
#4687
#6964
#7742
#8349

@eyedean
Copy link

eyedean commented Jan 30, 2021

I ran exactly into this and I am gonna share my findings, in case it helps @pdanpdan.

My Case

I have a server (that I load index.html's content from with history fallback support) at http://mydomain.com/myapp/ and I want the assets (JS/CSS/Images) to be served from my CDN at http://cdn.mydomain.com.

Note that this set-up shouldn't be uncommon -- assets in production should always be served from CDN as they serve no logic at all, and shouldn't cost processing power or data-transfer costs of a server with CPU. However, the initial request (index.html content) can be served from the web-servers for:

  • User controlling (e.g. block certain IPs, read various Cookies)
  • Creating sessions on the first request to the server.
  • Further server-side logic/navigation/redirect/etc.
  • Support history fallback via connect-history-api-fallback.

The problem

I found that I need to set cfg.build.publicPath (publicPath in build of quasar.conf) to point to CDN for index.html to load scripts and css files from CDN. (i.e. prefix the URL in <script src="http://cdn.mydomain.com/js/vendor.0123abcd.js"></script>).

On the other hand, apparently <base> tag (which is filled by fillBaseTag in plugin.html-addons.js) is needed internally for loading assets. It is also aggressively set to cfg.build.publicPath if it's not explicitly set.

However, vue-router is not a big fan of putting domain name in the <base> tag! (See vue-router issue #3372.) They strip out the domain name from the path (here), before the first replaceState in setupScroll. So it becomes replaceState(..., "/only/relative/path").

Now, since the base tag is pointing to CDN and vue-router is passing the relative-path of the current location to replaceState (with the above strip out), browser assumes the target belongs to the CDN, while the current document is at the main site. And this cross-domain pushState causes the security DOMException.

My Findings (so far)

  • vueRouterBase config param of quasar-conf.js, which has a fallback to publicPath in terms of absence, is being passed to vue-router as the base parameter in the construction options.

  • By setting appBase: "/myapp/", I no longer get that router exception. However, my assets (e.g. background image) are not pointing to the CDN anymore, and they are 404 because they are requested from the main site.

  • Since the documentation on quasar-conf.js for these variables (publicPath, appBase, and vueRouterBase) are relatively minimal (mainly "do not mess up!" :D), I am trying to learn from the Quasar's code as well as commits/issues here and there.
    I have found that:
    • A) @rstoenescu says (Sep 2019 though) that publicPath shouldn't contain the full URL. (doc note is a bit confusing.)
    • B) vue-router doesn't like <base> (populated via appBase) to contain a full URL either.
    • ... So A + B => I don't know what's the best way to feed CDN's domain-ful URL into the conf file. Maybe there should be a new config parameter for that?

@pdanpdan
Copy link
Collaborator

@eyedean Doesn't setting ASSET_PATH work for your case?
https://webpack.js.org/guides/public-path/

@eyedean
Copy link

eyedean commented Jan 30, 2021

@pdanpdan unfortunately, it doesn't work as expected.

I set that up properly (using build > env in quasar.conf.js) but the assets in the index.html don't respect that. They css and js assets in index.html (namely <link href=/css/app.abcd1234.css> tags and <script src=/js/app.abcd1234.js></script>) have the relative path, starting with /.

I put the following in quasar.conf.js

build: {
   ...,
   env: {
       ASSET_PATH: "http://cdn.mydomain.com/my/path"
   },
   ...
}

... and confirmed that it gets populated in process.env via parse-build-env.js. I put a console.log({ env }) in that file, and confirmed process.env.ASSET_PATH is populated properly.

PS. I didn't set publicPath of quasar.conf.js to anything during this test.

PPS. I personally feel that publicPath is so overused in the configs and bears so many different meanings. Breaking into several more-specific variables (e.g. an individual variable like assetPrefix in Next.js for my case) can really help developers, in my opinion.

@eyedean
Copy link

eyedean commented Jan 31, 2021

Update: I managed to get it to work in a way and unblock myself! 🎉

  • I needed publicPath to point to CDN (full domain) in order to have the full URL in index.html's assets (js and css).
  • Having publicPath and no (void 0) appBase causes appBase to get the publicPath's value (quasar-conf-file.js#L526-L529. But we don't want <base> tag to be populated with a domain-ful URL, as it causes vue-router to freak out and throw that cross-domain DOMException.
  • Luckily, the check to fill the base tag is just checking for the truthiness of cfg.build.appBase at plugin.html-addons.js#L38-L40.

It means that if we set publicPath to CDN path, and appBase to an empty string (""), we get the CDN path in assets of index, but no <base> tag, exactly as we needed. We can send the base path for the vue-router via vueRouterBase, separately (in case the serving path on the server is not /). So, it all worked perfectly for me!


PS. I know it's such a fragile hack. I believe ideally there should be a new config variable (e.g. assetsPath) that only serves for the purpose of prefixing the assets. (e.g. passed directly to webpack's ASSET_PATH; I don't know why { env: ASSET_PATH: "<path>" } didn't work for me. Didn't dig further.) That way what we should ideally have would be:

  • Assets and index.html are both served from the same external server (e.g. CDN): use publicPath (still having full URL in fallbacked appBase would not be suggested per vue-router.)
  • Assets are served from CDN, but not index.html: use assetsPath for CDN and vueRouterBase for the index.html's relative path to the server. (assuming the developer doesn't like <base> tag at all due to its holistic side-effects.)

Thanks!

@eyedean
Copy link

eyedean commented May 6, 2021

I am back in here to add another note on a similar problem.

Having relative (subdirectory) path on dev mode

If you want your dev server to load your assets from /admin (to have parity with Production) you need to add:

const LOCAL_BASE_PATH = `/admin`;
// ...
// then, down in the build section:

build: {
    chainWebpack(cfg) {
        if (ctx.dev) {
            cfg.output.publicPath(LOCAL_BASE_PATH);
        }
    },
    ...(ctx.dev ? { publicPath: LOCAL_BASE_PATH } : {}),
    appBase: "",

(I mentioned it in discord too.)

By default dev server serves assets using no path (i.e. <script src="vendor.js">) which breaks if you land on /admin/user/100 path (i.e. reload, historyMode: HTML5) and becomes a 404.

Idea for configs

It would be great to have a devPublicPath parameter that would do all of this.

@pschrammel
Copy link

Hi,
I tried to get at least dev running and got this far:

build: {
  ueRouterMode: 'history',
  publicPath: '/ui/',
  vueRouterBase: '/ui/',
  chainWebpack(cfg) {
      cfg.output.publicPath('/ui/');
  },
  appBase: "",
}
devServer: {
   https: false,
   public: 'https://example.com/ui/', 
   publicPath: '/ui/',
   sockPath: '/ui/sockjs-node/',
}

the router config ignores /ui/ and starts right at root ('/').
so far /ui/ is working:

  • index.html, app.js, vendor.js are loaded correctly, the app initializes
  • socks-node is also working

BUT: any subpath path (like /ui/test) are responded by 404
any idea? currently quite a blocker.

quasar version: 1.15.13

@eyedean
Copy link

eyedean commented May 14, 2021

@pschrammel See this answer on stackoverflow to get a good grasp of why 404s can happen in SPA (e.g. devServer of Quasar.)

You will also need to apply what I mentioned right above your post, if you want to serve your devServer from a subdirectory.

@pschrammel
Copy link

I think I did everything you wrote in your upper message.
Perhaps let's go one by one:
Backend dev server:
when the browser requests /ui/statics/icons/favicon-16x16.png the dev server returns 404; the Host and Filename are correct, a curl request is also returning a 404. So the dev server doesn't seem to be configured correctly.

@eyedean
Copy link

eyedean commented May 23, 2021

@pschrammel You might not have much luck receiving 1:1 support on your case here. I'd suggest Quasar's Discord channel, Forum, or Stackoverflow.com

Additionally, here are some tips for you to start debugging on your own:

  • Find out if that png file is served at all, even at a different URL.
  • Find out if anything (including landing on initial index.html files) works there and is not a 404.
  • If you can, put console.log in Quasar's code (e.g. in node_modules/\@quasar/app/lib/webpack/create-chain.js) to both learn some internals of Quasar, and see how your variables affect the Webpack's output.
  • Last but not least, start with a fresh repo (created via Quasar CLI in 2 minutes) and make it work the way you want, with minimal changes. If that doesn't work, then you can share it with other people to help you debug. (and it might actually be a real bug.) But if that minimal repo from scratch did actually work as expected, then you can apply your own custom configs, one piece at a time, until it breaks. You will then know what's the piece to blame!

Good luck!

@rstoenescu
Copy link
Member

The publicPath should never be set to a CDN because it will break the whole app. Instead, all images or other assets that you store on CDN should have the full URL specified (which points to the CDN).

@Dokinz
Copy link

Dokinz commented Nov 22, 2021

The publicPath should never be set to a CDN because it will break the whole app. Instead, all images or other assets that you store on CDN should have the full URL specified (which points to the CDN).

@rstoenescu
How to set the full URL on js file?
Works in nuxt:
https://nuxtjs.org/docs/configuration-glossary/configuration-build/#publicpath
Works in next:
https://nextjs.org/docs/api-reference/next.config.js/cdn-support-with-asset-prefix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants