Solvedangular cli Angular CLI 6.0.0 --base-href, --deploy-url no longer works as expected

I'm submitting a...

[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:

Current behavior

After upgrading to Angular 6 the --base-href and --deploy-url no longer work as expected.

I am running ng build --base-href /virtualDir --deploy-url /virtualDir --prod command to build the app which used to work in angular 5.2 without a problem. Now it tries to grab runtime.js, styles.js, main.js and polyfill.js without adding / after the base-href so it just ends up getting a 404 error.

Used to be: https://mydomain.com/virtualDir/styles.08dcsetc.css
Now it is: https://mydomain.com/virtualDirstyles.08dcsetc.css

Notice the missing / between virtualDir and styles.08dcsetc.css

If i add the / to the end of base-href and deploy-url, the main files get loaded but then every call to an API fails because and extra / gets added to every request.

The api calls look like this:
https://mydomain.com/virtualDir//api//someEndpoint

Expected behavior

Should load all required files from https://mydomain.com/virtualDir/

Minimal reproduction of the problem with instructions

Not sure how to reproduce this on stackblitz.com.

But this is the simples one i can come up with.

  • Create virtual directory on your server
  • Have a node instance running in that directory with following setup
const express = require('express');
const api = require('./api');
const path = require('path');

const app = express();
const port = process.env.PORT || 3000;
const virtualDirPath = process.env.virtualDirPath || '';

// static files (all angular stuff)
app.use(virtualDirPath, express.static(path.join(__dirname, 'dist')));
// Handles all api routes
app.use(`${virtualDirPath}/api`, api);

app.listen(port, () => {
  console.log(`listening on ${port}`);
});
  • Build and deploy angular app to said virtual directory with --base-href and --deploy-url set to your virtual dir
  • Try to run it.

What is the motivation / use case for changing the behavior?

Would like to be able to run multiple apps on same domain within virtual directories.

Environment

Angular version: 6.0.0
Angular CLI 6.0.0
Node: 8.11.1


Browser:
- [X] Chrome (desktop) version XX
- [X] Chrome (Android) version XX
- [X] Chrome (iOS) version XX
- [X] Firefox version XX
- [X] Safari (desktop) version XX
- [X] Safari (iOS) version XX
- [X] IE version XX
- [X] Edge version XX
 
For Tooling issues:
- Node version: 8.11.1  
- Platform:  Mac 

Others:
20 Answers

✔️Accepted Answer

I added "baseHref": "/dir" to projects.Angular6.architect.build.options within a newly created Angular6 project's angular.json.

The option is ignored and the site is still accessing everything under root.

If you run ng serve --base-href=/dir, it used to show the message

open your browser on http://localhost:4200/dir/

But now, when running ng serve with the above configuration changes, the dir/ part is simply missing.

(As a side note, I feel a massive lack of documentation for the new build/configuration system, let alone migration documentation.)

Other Answers:

I found that the deploy-url works correctly with ng build, but is completely ignored with ng serve. Can anyone confirm it?

For a base HREF (as per the specification), the meaning of /virtualDir and /virtualDir/ are different. The first signifies the last segment is a file and the second signifies that the last segment is a directory. Both are valid base HREFs.

For the use case outlined above, ng build --base-href /virtualDir/ --prod should be sufficient. The deploy URL option is essentially unnecessary if using the base HREF option. For a comparison, please see https://github.com/angular/angular-cli/blob/master/docs/design/deployurl-basehref.md

I think this needs more attention. Took us days to get the configuration just right, so I share the frustration of the OP. I will share our working experience:

Adding to @zcsongor, if you want HMR to work, you also need to add "baseHref": "/dir/" and "deployUrl": "/dir/" to {projectname}.architect.serve.configurations.{env} or {projectname}.architect.serve.options because ng serve will need to be told too.

This is very relevant when you want to serve Angular from within Asp.NET core, where the app should be distributed in "wwwroot/dist"

in angular.json, our {projectname}.architect.build.options looks like this:

"outputPath": "wwwroot/dist",
"baseHref": "/dist/",
"deployUrl": "/dist/",
"index": "ClientApp/index.html",
"main": "ClientApp/main.ts",
"polyfills": "ClientApp/polyfills.ts",
"tsConfig": "ClientApp/tsconfig.app.json",
"assets": [
	"ClientApp/resources"
],
"styles": [
	"ClientApp/styles.scss"
],
"scripts": [
	"node_modules/jquery/dist/jquery.min.js",
	"node_modules/popper.js/dist/popper.min.js",
	"node_modules/bootstrap/dist/js/bootstrap.min.js"
]

and in angular.json, our {projectname}.architect.serve.options looks like this:

"browserTarget": "PROJECT NAME:build",
"baseHref":  "/dist/",
"deployUrl":  "/dist/",
"hmrWarning": false

and if you use ASP.NET core and want HMR and LazyLoad to work, you will want to add hmr config as shown here https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/configure-hmr.md in your angular.json and use a conditional block MapWhen() like shown below to send all the JS and websocket calls to the ng serve process in the background. (pardon the poor regex)

In Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ... other service config above...

    // configuration so UseSpaStaticFiles works (see below)
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "wwwroot/dist";
    })
};

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ... other stuff above ...
	
    if (env.IsDevelopment())
    {
        // send all the Javascript queries to the node process, along with the websocket calls from devpack
        app.MapWhen(x => Regex.IsMatch(x.Request.Path.ToUriComponent(), "^/(.*.js|sockjs-node/.*)"), (app2) =>
        {
            app2.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";
                // add script "start-hmr": "ng serve --configuration hmr" to package.json
                spa.UseAngularCliServer(npmScript: "start-hmr"); 
                // alternatively, with a separate ng serve, but we never got this to work
                //spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
            });
            // end of the pipeline for javascript files or web sockets.
        });

        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseSpaStaticFiles();

        // must be last.
        app.UseMvc(routes =>
        {
            // MVC hook up so we can process /api calls and the home controller initial view.
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");

            // required so the 404s are sent to the browser app. ex: /system/users may be a valid route for the browser, but not MVC
            routes.MapSpaFallbackRoute(
                name: "spa-fallback",
                defaults: new { controller = "Home", action = "Index" });
        });
    }
    else
    {
        // must be first
        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseSpaStaticFiles();

        // production => no spa services.
        // setup the MVC routes that we will be serving.
        app.UseMvc(routes =>
        {
            // MVC hook up so we can process /api calls and the home controller initial view.
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");

            // required so the 404s are sent to the browser app. ex: /system/users may be a valid route for the browser, but not MVC
            routes.MapSpaFallbackRoute(
                name: "spa-fallback",
                defaults: new { controller = "Home", action = "Index" });
        });
    }
}

More Issues: