Angular-cli: Avoiding app rebuild for a different deployUrl, dynamic deployUrl resolution at runtime

Created on 10 Jan 2020  路  7Comments  路  Source: angular/angular-cli

馃殌 Feature request

Being able to deploy angular apps in different environments without necessarily having to rebuild them. I found several related issues, but none of them was closed in a satisfactory way. It seems a lot of people also confuse the purpose of baseHref and deployUrl.

Command (mark with an x)

  • [ ] new
  • [ x ] build
  • [ ] serve
  • [ ] test
  • [ ] e2e
  • [ ] generate
  • [ ] add
  • [ ] update
  • [ ] lint
  • [ ] xi18n
  • [ ] run
  • [ ] config
  • [ ] help
  • [ ] version
  • [ ] doc

Description

At the moment, the only reliable option I could find to make sure my application sources can be served from a CDN is to leverage the deployUrl option offered by the CLI, which is in turn using output.publicPath from Webpack.

The current implementation implies that the final CDN url needs to be known at build time, and that deployment to a different CDN (ie. DEV vs PROD environment) requires a rebuild of the application.

This is IMHO not acceptable, since it prevents me to reuse the same artifacts when promoting a build across DTAP environments, and impacts the portability of an application.

While I could figure out an easy alternative for environment options and baseHref, with a bit of support from SSR and nginx, I couldn't work around the issues i found with deployUrl.

Describe the solution you'd like

Developers should be allowed to inject custom asset url resolver logic that allows to retrieve and consume the deployUrl at runtime, not only statically at buildtime. This should apply both to lazy-loaded modules and possibly to resources referenced in SASS files.

Perhaps deriving the right URL of the CDN by using the value of a custom <meta> tag in the index.html could be a very convenient way to have it dinamically resolved by the webserver. In alternative, it should be possible to customize the provider responsible to retrieve such value at runtime, similarly to what we can do with https://angular.io/api/common/APP_BASE_HREF

Describe alternatives you've considered

I tried a custom webpack builder and I had some success using __webpack_public_path__ for lazy-loading of modules. This was not enough to correctly resolve references to external resources in scss files.

devkibuild-angular triage #1 feature

All 7 comments

@filipesilva happy to have a look myself at possibilities here, could you offer some guidance to figure out how the "deployUrl" parameters ends up being used by the scss loader?

The deploy URL option is fundamentally a build time construct. It is not intended to be a runtime controllable value nor can it as it is injected throughout the output files. This includes the HTML index file via the application script locations and CSS files (both global and component) which must hard-code the value to be considered valid CSS.

The HTML base HREF, however, is the exact opposite and is instead a runtime construct that is applied by the browser itself. It exists in only one location within the entire application (the HTML index file). Its primary purpose is to alter the retrieval location of relatively referenced resources. It can also be dynamically adjusted at runtime by modifying the value in the DOM. An inline script within the head element, for instance, could be used to alter the value based on some criteria before any other scripts/resources are loaded.

Filed angular/angular#34931 to update docs to be more clear about the distinction between deployUrl and baseUrl. We've seen other users make the same mistake.

@dgp1130 I understand the distinction between deployUrl and baseUrl very clearly. Even if that's not the issue here, I've also seen a lot of people confuse the two concepts and that will definitely be useful.

@clydin I am using nginx config to replace a placeholder of my choosing within the index.html that I inject through the deployUrl configuration so that the scripts are loaded correctly, and I used the extendable webpack builder to inject a custom publicPath that uses the same injected value for lazy-loading scripts.

Wouldn't relative url(...)s still be a viable option for the CSS? That way the only place where we need to handle a dynamic deployUrl with a placeholder is the index.html

I am going mad trying to figure this out. We deploy each language to a CDN. So the script source and css source needs to be a different URL than where the HTML lives.

So https://qa.website.com/de/index.html needs to serve scripts from https://qa.website.com/static/de/.

<script src="https://qa.website.com/static/de/runtime-es2015.2be276be3644e2c28571.js" type="module"></script><script sr

I actually ended up having to put a placeholder ('firefly') in the deployURL and then change the files post-build with the each locale.

`

let allLocales = fs.readdirSync('./dist/browser', 'utf-8');
for(locale of allLocales){
    let indexPath = './dist/browser/' + locale + '/index.html';
    let indexHtml = fs.readFileSync(indexPath, 'utf-8');
    let newIndex = indexHtml.replace(/firefly/gm, 'https://qa.somesite.com/static/' + locale + '/');
    fs.writeFileSync(indexPath, newIndex, 'utf-8');

    let allFiles = fs.readdirSync('./dist/browser/' + locale);
    allFiles.forEach(fileName => {
        if (fileName.startsWith('runtime-es5')){
            let es5Path = './dist/browser/' + locale + '/' + fileName;
            let es5File = fs.readFileSync(es5Path, 'utf-8');
            let newEs5 = es5File.replace(/firefly/gm, 'https://qa.somesite.com/static/' + locale + '/');
            fs.writeFileSync(es5Path, newEs5, 'utf-8');
        }

        if (fileName.startsWith('runtime-es2015')){
            let es2015Path = './dist/browser/' + locale + '/' + fileName;
            let es2015File = fs.readFileSync(es2015Path, 'utf-8');
            let newEs2015 = es2015File.replace(/firefly/gm, 'https://qa.somesite.com/static/' + locale + '/');
            fs.writeFileSync(es2015Path, newEs2015, 'utf-8');
        }
    })

}`

Wouldn't relative url(...)s still be a viable option for the CSS? That way the only place where we need to handle a dynamic deployUrl with a placeholder is the index.html

When using relative url()'s (a recommended and common practice) in stylesheets, the deploy URL will be hard-coded across both global and component stylesheets. Global stylesheets then become standalone CSS files and component stylesheets are processed by the AOT compiler and embedded within the code. Trying to adjust these values after the fact is not only nontrivial but error-prone.

The deploy URL option is primarily intended to support edge cases where a base HREF (and potentially APP_BASE_HREF token) will not work (e.g., legacy situations with ngUpgrade and AngularJS).

If the deploy URL option is necessary than individual project configurations can be used for each locale which will allow a unique deploy URL per locale. The build speed will decrease but this is the unfortunate cost of using an option that embeds itself throughout the application. This is also the reason the base HREF is generally the better option; it only exists in one easily adjustable location (the 'index.html' file for the application).

@santhony7 For that use case, the deploy URL shouldn't be necessary. This may need to be adjusted based on project configuration and settings but some form of the following should work.
1) Set the baseHref for the project to /static/
2) Add a factory provider for APP_BASE_HREF to the project's main module similar to the following:

{ 
      provide: APP_BASE_HREF, 
      useFactory: (locale) => `/${locale}/`, // perform any adjustments here (trim to just first segment, etc.)
      deps: [LOCALE_ID]
    }

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._

Was this page helpful?
0 / 5 - 0 ratings