angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.73k stars 11.98k forks source link

Add `--deploy-url` to `application` builder #26411

Closed TheTomasJ closed 6 months ago

TheTomasJ commented 10 months ago

I am referencing following response from @dgp1130, where is stated that "team does not expect that any existing use cases to suddenly become unsupported". From v17 there is no possibility to reference static assets with absolute url. Using base hrefs is not proper replacement because relative links must stay relative to root when users wants to open links in new tab (ctrl + click) and also for non javascript consumers of server side rendered pages. Combinations with APP_BASE_HREF are not applicable for this use case. Due to this we believe this is regression for no good reason.

          We are still planning to remove `deployUrl` eventually largely because of all the confusion it causes. The exact version it gets dropped is still TBH, but I can confidently say it will *not* be removed in v14, so this shouldn't be an immediate problem for anyone. The blog post in particular should hopefully help address common use cases which have used `deployUrl` in the past and how we recommend moving forward for each of them. We're not expecting any existing use cases to suddenly become unsupported, just that the right approach for you will probably vary by your particular requirements.

Originally posted by @dgp1130 in https://github.com/angular/angular-cli/issues/22113#issuecomment-1099723870

Updated 23.2.2024 with minimal reproduction: https://github.com/TheTomasJ/angular-router-example

jdwill916 commented 9 months ago

We continued using Angular as our frontend because of this promise. Due to having a MVC Razor base page with important functionality for our site, we have used the technique of injecting our index.html into a razor page. This means that our .js and .css files are not hosted at the same URL as the base page. We have had some success with manually adding the URLs of the .js and .css files to our page, but this technique does not work with lazy loaded modules. I would imagine users that host their files on a CDN would have similar issues.

dgp1130 commented 8 months ago

Had some discussion on this today. Our general stance is that --deploy-url is discouraged and we have found <base href> to be a less confusing alternative: https://angular.dev/tools/cli/deployment#--deploy-url. To be clear we still have not removed --deploy-url, it still exists in the browser and browser-esbuild builders.

We've been hesitant to add --deploy-url to application builder unless we find that it is a significant migration blocker for many applications. However, I think this issue would be helpful to collect feedback from the community, so I'll leave it open for now.

If moving off --deploy-url is viable but particularly challenging for you, then please thumbs up the issue so we know it's important to unblock adoption of application builder.

Separately, if you have a specific use case which requires --deploy-url (and is incompatible with <base href>), please post your use case here. We can use this data to inform the longer term future of --deploy-url and what use cases we might need to improve support for.

dlh2 commented 8 months ago

Hi, if it's discouraged... Can the docs be updated?

https://angular.io/guide/deployment

On the footer says Last reviewed on Mon Feb 28 2022

So perhaps new users need to understand why it doesn't work

CorentinDeBoisset commented 8 months ago

I'm creating a simple flask application, and don't want to manage the frontend assets with an external server.

Without setting baseHref, the built assets are expected to be requested from URLs like /main.<hash>.js. The only way to configure flask to serve the asset in this location is to set static_url_path="/". We get a route list such as this:

Endpoint                            Methods  Rule
----------------------------------  -------  --------------------------------------
home.catch_all_route                GET      /
home.catch_all_route                GET      /<path:path>
static                              GET      /<path:filename>

Unfortunately, when defining this, the static_route overrides the catch_all that serves the index.html. When navigating to a frontend route, the server logically returns a 404 (it tried to find an asset file for that route and failed).

The hack by setting baseHref="/static" and APP_BASE_HREF="/" would work, but breaks all CTRL-click, which is 100% not acceptable (I mean, it's a dealbreaker, the kind that leads to migrate to another framework).


The only solution I found is to build using the browser builder as @dgp1130 suggested, set "deployUrl": "static/" and leave baseHref undefined. This way, Flask can be configured with static_url_path="/static" which gives the following routing table:

Endpoint                            Methods  Rule
----------------------------------  -------  --------------------------------------
home.catch_all_route                GET      /
home.catch_all_route                GET      /<path:path>
static                              GET      /static/<path:filename>

I get a warning that deployUrl is deprecated, which means that angular will stop working at some point :/

dgp1130 commented 8 months ago

Hi, if it's discouraged... Can the docs be updated?

https://angular.io/guide/deployment#the-deploy-url states:

base href is generally the better option.

https://angular.dev/tools/cli/deployment#--deploy-url also discourages it.

The hack by setting baseHref="/static" and APP_BASE_HREF="/" would work, but breaks all CTRL-click

What Ctrl+click behavior are your referring to? Do you mean ctrl+clicking a link where the browser opens it in a new tab? I don't see how --deploy-url or <base href> would break that behavior.

Unfortunately, when defining this, the static_route overrides the catch_all that serves the index.html. When navigating to a frontend route, the server logically returns a 404 (it tried to find an asset file for that route and failed).

I'm not familiar with Flask specifically, but if I'm understanding your comment correctly it seems like it is not feasible to configure the server to both serve a static directory and also fallback to index.html on 404? Those don't feel like mutually conflicting requirements and I personally would expect that to be possible in any server. Is there a Flask feature request to support this or any reasoning from the Flask team for rejecting such a feature?

doggy8088 commented 8 months ago

Just in case someone who don't know how to switch the builder from application to browser in the angular.json. Here is the diff for that:

image

jeremyj11 commented 8 months ago

Separately, if you have a specific use case which requires --deploy-url (and is incompatible with <base href>), please post your use case here. We can use this data to inform the longer term future of --deploy-url and what use cases we might need to improve support for.

My use case - I deploy my front-end to an app server, but then a CDN pull zone caches static assets and serves from the CDN. I use --deploy-url to set the CDN url.

eg My app is at domain.com CDN looks at domain.com and caches files to cdn.com I build with --deploy-url=cdn.com and upload to domain.com Users get index.html from domain.com, but all assets are served from cdn.com

dominicbachmann commented 7 months ago

We use angular/elements for building microfrontends with angular which each are hosted under a separate deploy url (e.g. /microservice-1, /microservice-2, etc...). The main chunk is loaded into our shell at runtime. The index.html of the microservices will never be loaded by the shell and therefore baseHref is not working.

xunjianxiang commented 7 months ago

--deploy-url is necessary. I just want to set cdn url, don't want to effect the router url.

They are different things. separate them. Don't make things complicated.

Mensu commented 7 months ago

The hack by setting baseHref="/static" and APP_BASE_HREF="/" would work, but breaks all CTRL-click

What Ctrl+click behavior are your referring to? Do you mean ctrl+clicking a link where the browser opens it in a new tab? I don't see how --deploy-url or <base href> would break that behavior.

@dgp1130 The ctrl+click would be broken when we use a relative link. Say we serve a site on http://www.aaa.com/bbb, and the user ctrl+click a link like <a href="foo"></a>, then with baseHref="/static/" the browser would open http://www.aaa.com/static/foo, but the url we want to open is http://www.aaa.com/foo. Similar use cases include but are not limited to window.open(link), window.location = link, fetch(link), ...

Of course we could use javascript to intercept these cases and build an absolute url for use, but it's cumbersome and not intuitive. SDKs with business logic that are shared among sites built on different web frameworks would have to take it into account if we want to adopt them in Angular apps. There are also scenarios without javascript like SSR (used by crawler and SEO).

It seems the reasons to deprecate --deploy-url are about build speed and bundle size. I think developers should have a chance to choose between "intuitive relative link support" and "better build speed & bundle size" if we couldn't figure out a better solution to achieve both.

dgp1130 commented 7 months ago

@dgp1130 The ctrl+click would be broken when we use a relative link. Say we serve a site on http://www.aaa.com/bbb, and the user ctrl+click a link like <a href="foo"></a>, then with baseHref="/static/" the browser would open http://www.aaa.com/static/foo, but the url we want to open is http://www.aaa.com/foo. Similar use cases include but are not limited to window.open(link), window.location = link, fetch(link), ...

@Mensu That sounds like the typical behavior of <base href> to me? Is the click behavior different from ctrl+click in this scenario? I suspect this might be because ctrl+click opens in a new tab and thus performs a server-side navigation whereas click performs a client-side navigation. However if that's a problem then it is likely a configuration issue with your server.

If you can create a minimal reproduction of a scenario where click and ctrl+click behavior are different, then I would be very interested to see it.

nathan-nusign commented 7 months ago

Our use case is that we host two versions of our site on one domain as we are slowly migrating to Angular. To facilitate this, we are using --deploy-url to store all of our resources on a path so that we can then use cloudfront rules to direct path/.* to our new angular s3, and *.* to the old s3 bucket. We have tried, but it seems like we can't accomplish this in any other way so we are currently not able to switch to the application builder.

Mensu commented 7 months ago

@Mensu That sounds like the typical behavior of <base href> to me? Is the click behavior different from ctrl+click in this scenario? I suspect this might be because ctrl+click opens in a new tab and thus performs a server-side navigation whereas click performs a client-side navigation. However if that's a problem then it is likely a configuration issue with your server.

@dgp1130 In fact the url opened by normal click is the same as ctrl+click, and I agree that's the typical behavior of <base href>, but I don't want this <base href> behavior to take effect when I use relative links because we share some codes from sites without <base href> and relative link resolution results are different between sites with <base href> and sites without <base href>.

jhades commented 7 months ago

My application is served in mydomain.com, and the CSS and Javascript chunks are served from the Cloudfront CDN for performance reasons.

Removing Deploy Url prevents this extremely common use case.

I think this option should be added back, as the removal was preventing us from upgrading to the new Angular 17 esbuild-based application builder, and our use case is extremely common.

So it must be affecting a lot of people.

Here is the script that I added to my build pipeline to add the CDN url manually to the bundles.

This allowed me to use the EsBuild build in production:


console.log(`Running apply-cdn-url.js...`);

const fs = require('fs');
const cheerio = require('cheerio');

// Load the HTML content
const htmlContent = fs.readFileSync('dist/browser/index.html.original', 'utf8');
const $ = cheerio.load(htmlContent);

// CDN prefix to be appended
const cdnPrefix = process.argv[2];

console.log(`Applying CDN URL: ${cdnPrefix} to the HTML file...`);

// Locate and modify script and CSS tags
$('script[src], link[rel="stylesheet"]').each(function () {

  let tagName = $(this).get(0).tagName;

  if (tagName === 'script') {
    let src = $(this).attr('src');
    if (!src?.startsWith('http')) {
      $(this).attr('src', cdnPrefix + src);
    }
  }
  else if (tagName === 'link') {
    let href = $(this).attr('href');
    if (!href?.startsWith('http')) {
      $(this).attr('href', cdnPrefix + href);
    }
  }

});

// Output or save the modified HTML
const modifiedHtml = $.html();
const outputPath = 'dist/browser/index.html';
console.log(`Saving modified HTML to ${outputPath}...`);
fs.writeFileSync(outputPath, modifiedHtml);
DvDruzhkov commented 7 months ago

We use a business scenario when we replace the history of pages via window.history.pushState. This is necessary so that when the user on the bank payment page presses the browser’s back arrow, he returns to the site page we need. But if the base hrev (cdn address) is different from the main domain, then we get the error "Uncaught DOMException: Failed to execute 'pushState' on 'History'"

karptonite commented 7 months ago

We also serve our application from one domain, and our CSS and javascript from cloudfront for performance purposes. I'd really like to upgrade to the application builder.

To be clear, at least as far as I can tell, our use case requires deployUrl and is incompatible with baseHref. This really seems to be a blocker for us to migrate to the application builder, which we'd like to do because it seems like the way forward for server-side rendering (something we are not doing yet).

clydin commented 7 months ago

To be clear, at least as far as I can tell, our use case requires deployUrl and is incompatible with baseHref. This really seems to be a blocker for us to migrate to the application builder, which we'd like to do because it seems like the way forward for server-side rendering (something we are not doing yet).

This use case should be possible with the combination of the baseHref option and the APP_BASE_HREF token. The baseHref option should be set to the CDN location and the browser will automatically use that for all relative requests including files used from the assets option (something deployUrl does not do). The APP_BASE_HREF token which controls the Angular router should be something similar to the following:

{ provide: APP_BASE_HREF, useValue: location.origin }

or the following if the app is located on a subpath:

{ provide: APP_BASE_HREF, useValue: new URL('/my-app', location.origin).href }

If the application is not using the Angular router than the APP_BASE_HREF token is unneeded. This token is really the Router base URL and, besides its default value being derived from the HTML base HREF if present, it is otherwise unrelated to the HTML base HREF.

If you encounter further issues with this setup, please open a separate issue with a minimal reproduction if possible.

clydin commented 7 months ago

While the HTML base HREF does affect most relative requests it does not affect all. However, while ctrl+click behavior with relative links may be a concern for some applications, some others may not have an issue. The aforementioned suggestion may provide a solution to some that does not require the use of a heavyweight solution such as deploy URL which hard codes a specific path throughout the entire application in numerous locations. This not only makes the application non-transportable but also does not fully cover all file reference usages (assets being a key one). It can also make certain development pipelines problematic such as test/staging/production environment promotion without full rebuilds. However, each application's requirements are unique and having multiple options to solve a particular problem can allow those requirements to be adequately met. And to support that, the deployUrl functionality will be brought to the new build system. Until this is released, the compatibility builder (browser-esbuild) currently supports the option as well as containing many of the performance benefits of the full new build system.

TheTomasJ commented 7 months ago

thanks for your effort and patience :)

The base-href purpose is different in W3C specification and in angular "recommended" way. I created minimal repo to proof that "recommended" way is not eligible in general. Deploy url is different construct and should not be deprecated. Please see my general example below:

https://github.com/TheTomasJ/angular-router-example

karptonite commented 7 months ago

To be clear, at least as far as I can tell, our use case requires deployUrl and is incompatible with baseHref. This really seems to be a blocker for us to migrate to the application builder, which we'd like to do because it seems like the way forward for server-side rendering (something we are not doing yet).

This use case should be possible with the combination of the baseHref option and the APP_BASE_HREF token. The baseHref option should be set to the CDN location and the browser will automatically use that for all relative requests including files used from the assets option (something deployUrl does not do). The APP_BASE_HREF token which controls the Angular router should be something similar to the following:

{ provide: APP_BASE_HREF, useValue: location.origin }

or the following if the app is located on a subpath:

{ provide: APP_BASE_HREF, useValue: new URL('/my-app', location.origin).href }

Thanks for that clear explanation. I hadn't previously understood exactly how we were intended to replace deployUrl. I'd tried it that way, then a couple others when that didn't work, never quite sure I was doing what I should.

Unfortunately, If I do what you suggest, it causes a couple of issues, most notably, that all of the relative links on the websites (that directly assign the href with href=) that users might click now point to the path on the CDN instead of one of our websites. Having such relative links is necessary because there are parts of our websites that exist outside of the Angular application; while we are slowly converting out legacy site to Angular, it is a work in progress.

In addition, other resources we load on the page via relative links (such as additional js files) also now improperly point to the CDN (which really only hosts the files deployed for Angular). Essentially, the only requests that should go to the CDN are those for files generated by the ng build.

Additionally, API requests to our back end which have relative URLS are also going to the CDN. This part I suppose I could fix with an interceptor, if the rest worked, but overriding the entire baseHref to point to a domain that hosts ONLY the code generated by ng build, then correcting everything else back to what it would have been without baseHref seems like the wrong approach.

What seems like the right approach is deployUrl, which seems to do exactly what we need to separate the Angular requests from all of the other requests.

I suppose I could open an issue for this, but it feels like it would just be a duplicate.

clydin commented 7 months ago

Based on what you have described, what would the deployUrl option need to do with its value to meet the needs of the application? For instance, is the Angular CLI generating the index.html for the application or is something else? Would adding a prefix to the injected scripts/styles be sufficient or is there more that would need to be done? Would a generated manifest file of build outputs be more useful so that a custom post-build step could generate an index that meets the exact requirements of the application? Or would more extensive index.html templating be a better option?

karptonite commented 7 months ago

The Angular CLI is generating our index.html file, mostly. We also insert some of our own necessary scripts and other content into the file as well, before we serve it from one of our main domains. Adding the prefix specified by deployUrl to all requests for files generated by ng build would, I think, do everything we need it to do. The only files we serve from the the deployUrl are the ones generated by ng build.

I don't think a manifest of file build outputs would be more useful, at least for our use case. A more extensive index.html templating option also would not help us here, I think, because what we add varies by endpoint, and has to be determined in our PHP application on our server.

As an aside, deployUrl is also really helpful for our development process. Each of our developers has a remote dev server with a full installation of our application (including both the Angular and non-Angular parts, etc), and runs ng serve on their local system. Developers set deployUrl to //localhost:4200/. We do have to rsync a copy of the generated index.html over to the dev server, but then it is possibly to run the application on the dev server exactly as if it were the production application, with localhost standing in for our CDN.

JamesInDenver commented 7 months ago

We have a different use case. Perhaps it's too edge case to consider for the new deployUrl option, but I'll share just in case a native solution can cover our needs as well. We do what many called out and have our app files hosted on a CDN domain (unique per environment) that differs from the domain serving the application to the user. However, we also do not use the CLI generated index.html file. We pull the generated resource tags (CSS, Script, and component tag) and inject them into an index.html file managed and hosted by our CMS. To get all this working without using the currently deprecated deployUrl option requires us to:

We haven't yet looked at moving to ng17 as we're waiting for angular-builders to add support for esbuild, but if we could simplify all of this with a deployUrl option and/or index templates, that'd be great.

tclyit commented 6 months ago

In my use case, there is micro frontend (angular element) that used as hostile component or remote app in none angular host apps and all resource files are not the same host app assets path. To deprecate this --deploy-url hits so much the current implementation. I vote not to deprecating this --deploy-url.

karptonite commented 6 months ago

Thanks to the team for adding this feature to the application builder! Definitely appreciated!

tclyit commented 6 months ago

Thank you very much for supporting and keeping this deployUrl.

tclyit commented 6 months ago

Hi @alan-agius4, I saw that your PR status above was merged and I'm wondering when or what version this deployUrl fix will be fully available? Thanks in advance.

alan-agius4 commented 6 months ago

@tclyit, it will be available in 17.3 which will be released as the latest version later on today.

angular-automatic-lock-bot[bot] commented 5 months ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.