This repository has been archived by the owner. It is now read-only.

Feature: generator prompt for html5mode support #433

Open
jjt opened this Issue Nov 6, 2013 · 68 comments

Comments

Projects
None yet
@jjt
Contributor

jjt commented Nov 6, 2013

Anyone who wants to use html5mode with generator-angular has three problems to solve after yo angular is used to create a project:

  1. Refreshing any page other than / results in 404 (index.html is only served from /)
  2. Relative script tags cause 404s upon a refresh of pages other than /
  3. Build blocks in index.html use relative scripts/ path, and the built index.html has relative script tags, as above

I think that having a prompt for html5mode and having the generator configure the project to support this out of the gate would be beneficial. I'm using Angular 1.2.0-8336b3a and generator-angular 0.4.0.

Solution to 1: Rewrite rule to index.html

This is solved by configuring a server with rewrite rules. This can be nginx/apache in front of grunt server, but that would mean setting up and/or configuring a server outside of ones project.

I prefer adding connect middleware as in #132, or this gist:

NOTE: This code is for v0.4.0. For v0.5.0+, try this instead this, as of 0.6.0-rc1

livereload: {
    options: {
      port: 9000,
      // Change this to '0.0.0.0' to access the server from outside.
      hostname: '0.0.0.0',
      middleware: function (connect) {
        return [
          modRewrite([
            '!(\\..+)$ / [L]'
          ]),
          lrSnippet,
          mountFolder(connect, '.tmp'),
          mountFolder(connect, yeomanConfig.app)
        ];
      }
    },
}

From the discussion surrounding #132, it sounds like there were some cases to consider and some errors that weren't resolved. That was 3 months ago though, so things might have changed.

This is working for me, deep link refreshing and all.

Solutions to 2 and 3

These are related. Three solutions that work:

  1. Reference script and css tags with an absolute url for both the script tags in the markup: <script src="/scripts/controller/myController.js"></script>, and the build comments: <!-- build:js /scripts/... -->. I've been doing this manually every time I generate a new component, and it works.
  2. Add <base href="/"> to the head, making all relative urls into absolute urls. This works, but there are some issues with the tag. Plus, it changes the behaviour of all relative urls and anchor links, which seems overbearing.
  3. Change the build scripts to reference /scripts/ as in 1. which takes care of production builds. For development and testing, add another rewrite rule like ^(.+)scripts/(.*) scripts/$1. This would also work, but I think solution 1. is the cleanest.

Thoughts?

@eddiemonge

This comment has been minimized.

Show comment
Hide comment
@eddiemonge

eddiemonge Nov 6, 2013

Member

after thinking about it more, using absolute (relative to domain root) paths does seem the best solution for problems 2 and 3 which would mean #432.

Problem 1 is an ongoing issue that probably does need to be addressed at some point.

Member

eddiemonge commented Nov 6, 2013

after thinking about it more, using absolute (relative to domain root) paths does seem the best solution for problems 2 and 3 which would mean #432.

Problem 1 is an ongoing issue that probably does need to be addressed at some point.

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 6, 2013

Contributor

Hmm... I can see where using absolute paths relative to the domain root wouldn't be the best default, such as someone serving an ng app from http://site.com/path/ng-app/. But I still think that an absolute url is the way to go here. Maybe what's needed is an app base url config option that people like me can set to / and the people of Site.com can set to /path/ng-app/. I don't count the <base> tag as a good solution, as it causes problems with anchor links. That would break any page that has a ToC, like Wiki articles.

Either we use absolute urls, or we force people to do more production server configuration (rewrite anything with ^(.+)scripts/(.*) to scripts_path/$1, etc). Can you see a way around that?

Contributor

jjt commented Nov 6, 2013

Hmm... I can see where using absolute paths relative to the domain root wouldn't be the best default, such as someone serving an ng app from http://site.com/path/ng-app/. But I still think that an absolute url is the way to go here. Maybe what's needed is an app base url config option that people like me can set to / and the people of Site.com can set to /path/ng-app/. I don't count the <base> tag as a good solution, as it causes problems with anchor links. That would break any page that has a ToC, like Wiki articles.

Either we use absolute urls, or we force people to do more production server configuration (rewrite anything with ^(.+)scripts/(.*) to scripts_path/$1, etc). Can you see a way around that?

@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Nov 8, 2013

👍

It's really annoying that I have to modify my Gruntfile.js so much to have HTML5 mode support. It also makes keeping it up to date with the latest changes in Gruntfile.js created by this generator really complicated.

BTW. I use this workaround that I found somewhere on Stack Overflow for the issue with <base href="/"> tag and anchor links:

angular.module('directives')
.directive 'scrollTo', ($location, $anchorScroll) ->
  restrict: 'A'
  link: (scope, element, attrs) ->
    element.on 'click', (event) ->
      event.stopPropagation()

      location = attrs.scrollTo
      previousHash = $location.hash()
      $location.hash(location)
      $anchorScroll()

      # Reset to the previous hash to keep any additional routing logic from kicking in
      $location.hash(previousHash)

In HTML use the directive instead:

<a scroll-to='anchor'>Anchor link</a>

szimek commented Nov 8, 2013

👍

It's really annoying that I have to modify my Gruntfile.js so much to have HTML5 mode support. It also makes keeping it up to date with the latest changes in Gruntfile.js created by this generator really complicated.

BTW. I use this workaround that I found somewhere on Stack Overflow for the issue with <base href="/"> tag and anchor links:

angular.module('directives')
.directive 'scrollTo', ($location, $anchorScroll) ->
  restrict: 'A'
  link: (scope, element, attrs) ->
    element.on 'click', (event) ->
      event.stopPropagation()

      location = attrs.scrollTo
      previousHash = $location.hash()
      $location.hash(location)
      $anchorScroll()

      # Reset to the previous hash to keep any additional routing logic from kicking in
      $location.hash(previousHash)

In HTML use the directive instead:

<a scroll-to='anchor'>Anchor link</a>
@pgilad

This comment has been minimized.

Show comment
Hide comment
@pgilad

pgilad Nov 12, 2013

Contributor

My solution for this is as follows:

  1. in app.js (routing) add the following:
    $locationProvider.html5Mode(true);
  2. in index.html add the following:

<base href="/">
in <head> tag
3. in server.js add the following general catch-all routing (to prevent that refresh problem)

//general routing
app.use(function (req, res) {
    res.sendfile(__dirname + '/app/index.html');
});

That way all my routing is handled in AngularJs RouteProvider... and Express only gets Ajax request (or if your have specific routing that you catch)

Contributor

pgilad commented Nov 12, 2013

My solution for this is as follows:

  1. in app.js (routing) add the following:
    $locationProvider.html5Mode(true);
  2. in index.html add the following:

<base href="/">
in <head> tag
3. in server.js add the following general catch-all routing (to prevent that refresh problem)

//general routing
app.use(function (req, res) {
    res.sendfile(__dirname + '/app/index.html');
});

That way all my routing is handled in AngularJs RouteProvider... and Express only gets Ajax request (or if your have specific routing that you catch)

@saamalik

This comment has been minimized.

Show comment
Hide comment
@saamalik

saamalik Nov 12, 2013

@jjt Your snippet for Solution 1 is out-of-date, I believe. lrSnippet, and mountFolder variables can't be found. Using generator-angular-0.6.0-rc1

saamalik commented Nov 12, 2013

@jjt Your snippet for Solution 1 is out-of-date, I believe. lrSnippet, and mountFolder variables can't be found. Using generator-angular-0.6.0-rc1

@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Nov 12, 2013

@saamalik Something like this should work with 0.6.0-rc1:

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  var modRewrite = require('connect-modrewrite')([
    '!\\.ttf|\\.woff|\\.ttf|\\.eot|\\.html|\\.js|\\.coffee|\\.css|\\.png|\\.jpg|\\.gif|\\.svg$ /index.html [L]'
  ]);
  var yeoman = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  grunt.initConfig({
    yeoman: yeoman,
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect, options) {
            return [
              modRewrite,
              require('connect-livereload')(),
              connect.static('.tmp'),
              connect.static(yeoman.app)
            ];
          }
        }
      }
   }
}

szimek commented Nov 12, 2013

@saamalik Something like this should work with 0.6.0-rc1:

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  var modRewrite = require('connect-modrewrite')([
    '!\\.ttf|\\.woff|\\.ttf|\\.eot|\\.html|\\.js|\\.coffee|\\.css|\\.png|\\.jpg|\\.gif|\\.svg$ /index.html [L]'
  ]);
  var yeoman = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  grunt.initConfig({
    yeoman: yeoman,
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect, options) {
            return [
              modRewrite,
              require('connect-livereload')(),
              connect.static('.tmp'),
              connect.static(yeoman.app)
            ];
          }
        }
      }
   }
}
@saamalik

This comment has been minimized.

Show comment
Hide comment
@saamalik

saamalik Nov 12, 2013

@szimek Thanks! I removed the require('connect-livereload')() line and everything worked.

saamalik commented Nov 12, 2013

@szimek Thanks! I removed the require('connect-livereload')() line and everything worked.

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 12, 2013

Contributor

Yeah, my snippet targeted v0.4.0. Thanks for the updated code!

Contributor

jjt commented Nov 12, 2013

Yeah, my snippet targeted v0.4.0. Thanks for the updated code!

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 15, 2013

Contributor

I've found that this solution has the least impact on the Gruntfile. It changes nothing and adds code in one place and one place only, connect.livereload.options:

grunt.initConfig({
  // ...
  connect: {
    // ...
    livereload: {
      options: {
        open: true,
        base: [
          '.tmp',
          '<%= yeoman.app %>'
        ],
        // Add this middleware function
        middleware: function (connect, options) {
          return [
            require('connect-modrewrite')(['!(\\..+)$ / [L]']),
            // One can also use something like this:
            // '!\\.ttf|\\.woff|\\.ttf|\\.eot|\\.html|\\.js|\\.coffee|\\.css|\\.png|\\.jpg|\\.gif|\\.svg$ /index.html [L]'
            connect.static('.tmp'),
            connect.static(grunt.config.data.yeoman.app)
          ];
        }
      }
    }
  }
  // ...
}); // End grunt.initConfig()
Contributor

jjt commented Nov 15, 2013

I've found that this solution has the least impact on the Gruntfile. It changes nothing and adds code in one place and one place only, connect.livereload.options:

grunt.initConfig({
  // ...
  connect: {
    // ...
    livereload: {
      options: {
        open: true,
        base: [
          '.tmp',
          '<%= yeoman.app %>'
        ],
        // Add this middleware function
        middleware: function (connect, options) {
          return [
            require('connect-modrewrite')(['!(\\..+)$ / [L]']),
            // One can also use something like this:
            // '!\\.ttf|\\.woff|\\.ttf|\\.eot|\\.html|\\.js|\\.coffee|\\.css|\\.png|\\.jpg|\\.gif|\\.svg$ /index.html [L]'
            connect.static('.tmp'),
            connect.static(grunt.config.data.yeoman.app)
          ];
        }
      }
    }
  }
  // ...
}); // End grunt.initConfig()
@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Nov 15, 2013

@jjt You don't really need base section if you provide your own middleware stack. Of course if you want to make minimum amount of changes you can just leave it there, but it's quite confusing to specify paths in 2 places. You probably could do something like this (untested):

livereload: {
  options: {
    base: [
      '.tmp',
      '<%= yeoman.app %>'
    ],
    middleware: function (connect, options) {
      return [
        [require('connect-modrewrite')(['!(\\..+)$ / [L]'])],
        options.base.map(function (path) { return connect.static(path); })
      ].reduce(function (a, b) { return a.concat(b); }); // flatten array
    }
  }
}

This should allow to set paths using base options. I created an issue for grunt-contrib-connect (gruntjs/grunt-contrib-connect#56) to be able to add middlewares to an existing stack (this way we wouldn't have to define connect.static at all), but it wasn't accepted. If the code above works, it's probably even shorter than adding your middleware to an existing stack.

Don't you need this custom middleware stack with modrewrite also for test and dist servers? If it's always the same maybe one could just put it in a function at the top:

var html5stack =  function (connect, options) {
  return [
    [require('connect-modrewrite')(['!(\\..+)$ / [L]'])],
    options.base.map(function (path) { return connect.static(path); })
  ].reduce(function (a, b) { return a.concat(b); }); // flatten array
};

and then just:

livereload: {
  options: {
    base: [...],
    middleware: html5stack
  }
}

BTW. Thanks for the tip with shorter rewrite rule.

szimek commented Nov 15, 2013

@jjt You don't really need base section if you provide your own middleware stack. Of course if you want to make minimum amount of changes you can just leave it there, but it's quite confusing to specify paths in 2 places. You probably could do something like this (untested):

livereload: {
  options: {
    base: [
      '.tmp',
      '<%= yeoman.app %>'
    ],
    middleware: function (connect, options) {
      return [
        [require('connect-modrewrite')(['!(\\..+)$ / [L]'])],
        options.base.map(function (path) { return connect.static(path); })
      ].reduce(function (a, b) { return a.concat(b); }); // flatten array
    }
  }
}

This should allow to set paths using base options. I created an issue for grunt-contrib-connect (gruntjs/grunt-contrib-connect#56) to be able to add middlewares to an existing stack (this way we wouldn't have to define connect.static at all), but it wasn't accepted. If the code above works, it's probably even shorter than adding your middleware to an existing stack.

Don't you need this custom middleware stack with modrewrite also for test and dist servers? If it's always the same maybe one could just put it in a function at the top:

var html5stack =  function (connect, options) {
  return [
    [require('connect-modrewrite')(['!(\\..+)$ / [L]'])],
    options.base.map(function (path) { return connect.static(path); })
  ].reduce(function (a, b) { return a.concat(b); }); // flatten array
};

and then just:

livereload: {
  options: {
    base: [...],
    middleware: html5stack
  }
}

BTW. Thanks for the tip with shorter rewrite rule.

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 15, 2013

Contributor

My only change to the generated Gruntfile is to add that 9-line middleware function to connect.options.livereload. I use the same variable as in base, which is yeoman.app so I don't think it violates DRY.

But, the other server targets need the middleware as well. I'd just been testing it on the dev server up until this point. I like the direction you're going though at the bottom of your post, I'll try something along those lines.

Np! I yanked it from this gist.

Contributor

jjt commented Nov 15, 2013

My only change to the generated Gruntfile is to add that 9-line middleware function to connect.options.livereload. I use the same variable as in base, which is yeoman.app so I don't think it violates DRY.

But, the other server targets need the middleware as well. I'd just been testing it on the dev server up until this point. I like the direction you're going though at the bottom of your post, I'll try something along those lines.

Np! I yanked it from this gist.

@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Nov 15, 2013

My only concern with calling connect.static for every path manually is that if generator adds a new path to base option, you'll have to update middleware function as well. Using options.base inside middleware function should handle it automatically.

szimek commented Nov 15, 2013

My only concern with calling connect.static for every path manually is that if generator adds a new path to base option, you'll have to update middleware function as well. Using options.base inside middleware function should handle it automatically.

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 15, 2013

Contributor

Yeah, as long as when the middleware function gets called the value of options.base is set to the correct target then everything should be fine.

Contributor

jjt commented Nov 15, 2013

Yeah, as long as when the middleware function gets called the value of options.base is set to the correct target then everything should be fine.

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 15, 2013

Contributor

Okay, this is what I ended up with in the connect.options task config. Seems to work.

connect: {
  options: {
    // ...
    // Modrewrite rule, connect.static(path) for each path in target's base
    middleware: function (connect, options) {
      var optBase = (typeof options.base === 'string') ? [options.base] : options.base;
      return [require('connect-modrewrite')(['!(\\..+)$ / [L]'])].concat(
        optBase.map(function(path){ return connect.static(path); }));
    }
  }
}
Contributor

jjt commented Nov 15, 2013

Okay, this is what I ended up with in the connect.options task config. Seems to work.

connect: {
  options: {
    // ...
    // Modrewrite rule, connect.static(path) for each path in target's base
    middleware: function (connect, options) {
      var optBase = (typeof options.base === 'string') ? [options.base] : options.base;
      return [require('connect-modrewrite')(['!(\\..+)$ / [L]'])].concat(
        optBase.map(function(path){ return connect.static(path); }));
    }
  }
}
@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Nov 15, 2013

It will be hard to make it any shorter than this :)

szimek commented Nov 15, 2013

It will be hard to make it any shorter than this :)

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 15, 2013

Contributor

I don't think I want it any shorter! As is, this almost feels too "clever".

Contributor

jjt commented Nov 15, 2013

I don't think I want it any shorter! As is, this almost feels too "clever".

@eddiemonge

This comment has been minimized.

Show comment
Hide comment
@eddiemonge

eddiemonge Nov 19, 2013

Member

started thinking about how to implement this. added a note in https://github.com/eddiemonge/generator-angular-api

Member

eddiemonge commented Nov 19, 2013

started thinking about how to implement this. added a note in https://github.com/eddiemonge/generator-angular-api

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 19, 2013

Contributor

Yup, gotta have enough interest in order to justify adding it.

I've been thinking about options for configurable asset path prefixes in both development and production, and I've come to think that a configurable asset prefix would be beneficial for production, in the case of an Angular app and its assets being stored somewhere other than the root (ex. http://mysite.com/our/ng/app/). The dev server doesn't have that problem, as it's served out of the root of a domain by default (http://127.0.0.1:9000/) and so / should work as a prefix for most people.

So with that in mind, here's how I picture it working:

$ Use html5Mode (/foo/bar) instead of hashMode (/#/foo/bar)? (y/N)
   # if y...
$ Asset base path for production (default: /)

We'll add a config var, assetSrcPrefix (str, default "") and have two config prompts assetBasePathProd (str, default:"/"), and html5mode (bool, default:false)

  1. If html5Mode, assetSrcPrefix = "/"
  2. Prepend assetSrcPrefix to all asset paths (js, css) in various files (wherever src="script/..." or href="styles/..." would be output)
  3. Prepend assetBasePathProd to all build targets (ex. build:js <%= assetBasePathProd %>scripts...)
  4. If html5Mode, add connect-rewrite in package.json
  5. If html5Mode, add rewrite rule to Gruntfile

I've done some testing on a freshly generated Angular project with the above changes (to the created index.html/Gruntfile, not the gen itself) and I've got a dev server that serves html5Mode properly, and a build that can reference any root-relative path.

One feature that I'd like to see is an absolute asset url base in the build targets. This naive attempt <!-- build:js http://my.static.server/scripts/plugins.js --> results in the correct script tag being output, but the file ends up at ng-gen-project/dist/http:/my.static.server/scripts/plugins.js.

Contributor

jjt commented Nov 19, 2013

Yup, gotta have enough interest in order to justify adding it.

I've been thinking about options for configurable asset path prefixes in both development and production, and I've come to think that a configurable asset prefix would be beneficial for production, in the case of an Angular app and its assets being stored somewhere other than the root (ex. http://mysite.com/our/ng/app/). The dev server doesn't have that problem, as it's served out of the root of a domain by default (http://127.0.0.1:9000/) and so / should work as a prefix for most people.

So with that in mind, here's how I picture it working:

$ Use html5Mode (/foo/bar) instead of hashMode (/#/foo/bar)? (y/N)
   # if y...
$ Asset base path for production (default: /)

We'll add a config var, assetSrcPrefix (str, default "") and have two config prompts assetBasePathProd (str, default:"/"), and html5mode (bool, default:false)

  1. If html5Mode, assetSrcPrefix = "/"
  2. Prepend assetSrcPrefix to all asset paths (js, css) in various files (wherever src="script/..." or href="styles/..." would be output)
  3. Prepend assetBasePathProd to all build targets (ex. build:js <%= assetBasePathProd %>scripts...)
  4. If html5Mode, add connect-rewrite in package.json
  5. If html5Mode, add rewrite rule to Gruntfile

I've done some testing on a freshly generated Angular project with the above changes (to the created index.html/Gruntfile, not the gen itself) and I've got a dev server that serves html5Mode properly, and a build that can reference any root-relative path.

One feature that I'd like to see is an absolute asset url base in the build targets. This naive attempt <!-- build:js http://my.static.server/scripts/plugins.js --> results in the correct script tag being output, but the file ends up at ng-gen-project/dist/http:/my.static.server/scripts/plugins.js.

@AlJohri

This comment has been minimized.

Show comment
Hide comment
@AlJohri

AlJohri Nov 21, 2013

just dropping a +1 here for "have enough interest in order to justify adding it" !

AlJohri commented Nov 21, 2013

just dropping a +1 here for "have enough interest in order to justify adding it" !

@zishe

This comment has been minimized.

Show comment
Hide comment
@zishe

zishe Nov 22, 2013

Thanks guys!

zishe commented Nov 22, 2013

Thanks guys!

@alexpeattie

This comment has been minimized.

Show comment
Hide comment
@alexpeattie

alexpeattie Nov 22, 2013

+1, this would be fantastic to have

alexpeattie commented Nov 22, 2013

+1, this would be fantastic to have

@shea256

This comment has been minimized.

Show comment
Hide comment
@shea256

shea256 commented Nov 25, 2013

+1

@karin-n

This comment has been minimized.

Show comment
Hide comment
@karin-n

karin-n Nov 25, 2013

+1 - would be fantastic to have it included

karin-n commented Nov 25, 2013

+1 - would be fantastic to have it included

@logstown

This comment has been minimized.

Show comment
Hide comment
@logstown

logstown Nov 25, 2013

+1 from me as well.

On Mon, Nov 25, 2013 at 4:40 PM, proactivity-nz notifications@github.comwrote:

+1 - would be fantastic to have it included


Reply to this email directly or view it on GitHubhttps://github.com/yeoman/generator-angular/issues/433#issuecomment-29245026
.

logstown commented Nov 25, 2013

+1 from me as well.

On Mon, Nov 25, 2013 at 4:40 PM, proactivity-nz notifications@github.comwrote:

+1 - would be fantastic to have it included


Reply to this email directly or view it on GitHubhttps://github.com/yeoman/generator-angular/issues/433#issuecomment-29245026
.

@dansowter

This comment has been minimized.

Show comment
Hide comment
@dansowter

dansowter Nov 28, 2013

Looks wonderful. Yes please

dansowter commented Nov 28, 2013

Looks wonderful. Yes please

@jjt

This comment has been minimized.

Show comment
Hide comment
@jjt

jjt Nov 28, 2013

Contributor

Well, looks like a fair bit of interest in this.

Ping @sindresorhus @passy @eddiemonge. I'd be willing to work on a PR for this as described in my comment. If you guys think it's a good approach, let me know and I'll get to it.

Contributor

jjt commented Nov 28, 2013

Well, looks like a fair bit of interest in this.

Ping @sindresorhus @passy @eddiemonge. I'd be willing to work on a PR for this as described in my comment. If you guys think it's a good approach, let me know and I'll get to it.

@jeongwooklee

This comment has been minimized.

Show comment
Hide comment
@jeongwooklee

jeongwooklee commented Dec 4, 2013

+1 (v0.6.0)

@zakdances

This comment has been minimized.

Show comment
Hide comment
@zakdances

zakdances commented Dec 28, 2013

+1

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Aug 11, 2014

@bobmash Did you have to use your middleware snippet? Just for reference.

stereokai commented Aug 11, 2014

@bobmash Did you have to use your middleware snippet? Just for reference.

@mangrish

This comment has been minimized.

Show comment
Hide comment
@mangrish

mangrish Aug 13, 2014

I don't know if this is still relevant or will help anyone but if you run the generator and want html5 push mode to work, this is how i did it:

  1. Install modrewrite: npm install connect-modrewrite --save-dev. At the time of writing I'm using 0.7.7 and grunt-contrib-connect 0.8.0
  2. Update your Gruntfile (I'm using Less and I've added my fonts to show how i get them served as well
      //....
        connect: {
            options: {
                port: 9000,
                // Change this to '0.0.0.0' to access the server from outside.
                hostname: 'localhost',
                livereload: 35729

            },
            livereload: {
                options: {
                    open: true,
                    middleware: function (connect) {
                        return [
                            require('connect-modrewrite') (['!(\\..+)$ / [L]']),
                            connect.static('.tmp'),
                            connect().use('/bower_components',connect.static('./bower_components')),
                            connect().use('/fonts', connect.static('./bower_components/bootstrap/dist/fonts')),
                            connect().use('/fonts', connect.static('./bower_components/font-awesome/fonts')),
                            connect.static(appConfig.app)
                        ]
                    }
                }
            },
            dist: {
                options: {
                    open: true,
                    base: '<%= yeoman.dist %>'
                }
            }
        }
  1. You may need html <base href="/"/> in your index.html.

That's it!

If you are having trailing / issues try the following regexp: '!(\\..+)$ /index.html [L]'.

I hope this may be of use to somebody :)

mangrish commented Aug 13, 2014

I don't know if this is still relevant or will help anyone but if you run the generator and want html5 push mode to work, this is how i did it:

  1. Install modrewrite: npm install connect-modrewrite --save-dev. At the time of writing I'm using 0.7.7 and grunt-contrib-connect 0.8.0
  2. Update your Gruntfile (I'm using Less and I've added my fonts to show how i get them served as well
      //....
        connect: {
            options: {
                port: 9000,
                // Change this to '0.0.0.0' to access the server from outside.
                hostname: 'localhost',
                livereload: 35729

            },
            livereload: {
                options: {
                    open: true,
                    middleware: function (connect) {
                        return [
                            require('connect-modrewrite') (['!(\\..+)$ / [L]']),
                            connect.static('.tmp'),
                            connect().use('/bower_components',connect.static('./bower_components')),
                            connect().use('/fonts', connect.static('./bower_components/bootstrap/dist/fonts')),
                            connect().use('/fonts', connect.static('./bower_components/font-awesome/fonts')),
                            connect.static(appConfig.app)
                        ]
                    }
                }
            },
            dist: {
                options: {
                    open: true,
                    base: '<%= yeoman.dist %>'
                }
            }
        }
  1. You may need html <base href="/"/> in your index.html.

That's it!

If you are having trailing / issues try the following regexp: '!(\\..+)$ /index.html [L]'.

I hope this may be of use to somebody :)

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Aug 13, 2014

@mangrish You shouldn't need to manually include the fonts, they should already be served (they are automatically included via bower,json)

stereokai commented Aug 13, 2014

@mangrish You shouldn't need to manually include the fonts, they should already be served (they are automatically included via bower,json)

@mangrish

This comment has been minimized.

Show comment
Hide comment
@mangrish

mangrish Aug 13, 2014

@stereokai In step 2 i mentioned I'm using Less. I exclude the the css in the wiredep task (otherwise it gets added as a build artifact when deploying to production) and import the bootstrap and font-awesome less files in my own main less file. To get around the fonts not being in the right spot for font awesome i modify the @fonts variable which results in needing them served up in a particular spot! You can see a similar technique used by another generator: https://github.com/robinpokorny/generator-lessapp. My version isn't exactly the same but it works.

mangrish commented Aug 13, 2014

@stereokai In step 2 i mentioned I'm using Less. I exclude the the css in the wiredep task (otherwise it gets added as a build artifact when deploying to production) and import the bootstrap and font-awesome less files in my own main less file. To get around the fonts not being in the right spot for font awesome i modify the @fonts variable which results in needing them served up in a particular spot! You can see a similar technique used by another generator: https://github.com/robinpokorny/generator-lessapp. My version isn't exactly the same but it works.

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Aug 13, 2014

@mangrish Thanks for sharing. I'm not using Less, but others who do might find it useful for sure.

stereokai commented Aug 13, 2014

@mangrish Thanks for sharing. I'm not using Less, but others who do might find it useful for sure.

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Aug 17, 2014

An update: html5mode with sourcemaps

Compass 1.0.0 has just been released last night, with much-anticipated support for.... sourcemaps!

It is very easy to get sourcemaps working. The steps to enable sourcemaps with this generator are:

  1. Uninstall your current compass and sass with Ruby gem.
  2. gem install compass.
  3. In your Gruntfile, in Compass options block, add sourcemap: true.
  4. In the Autoprefixer options block, add map: true.
  5. Rewrite /app/styles to local path ./app/styles.

If you read no. 5's "rewrite" and had visions of hours and hours of messing around with Gruntfile and npm and scouring SO for answers - fear not: I've got you covered. Just copy this snippet above to your Gruntfile (replace your current connect settings).

Et viola! Sourcemaps are operational.

stereokai commented Aug 17, 2014

An update: html5mode with sourcemaps

Compass 1.0.0 has just been released last night, with much-anticipated support for.... sourcemaps!

It is very easy to get sourcemaps working. The steps to enable sourcemaps with this generator are:

  1. Uninstall your current compass and sass with Ruby gem.
  2. gem install compass.
  3. In your Gruntfile, in Compass options block, add sourcemap: true.
  4. In the Autoprefixer options block, add map: true.
  5. Rewrite /app/styles to local path ./app/styles.

If you read no. 5's "rewrite" and had visions of hours and hours of messing around with Gruntfile and npm and scouring SO for answers - fear not: I've got you covered. Just copy this snippet above to your Gruntfile (replace your current connect settings).

Et viola! Sourcemaps are operational.

@hpowers

This comment has been minimized.

Show comment
Hide comment
@hpowers

hpowers Aug 21, 2014

@stereokai where does the flags.silent come from in your snippet? When I try to use it I get a grunt error that >> ReferenceError: flags is not defined

hpowers commented Aug 21, 2014

@stereokai where does the flags.silent come from in your snippet? When I try to use it I get a grunt error that >> ReferenceError: flags is not defined

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Aug 21, 2014

@hpowers

I keep the following in my Gruntfiles, it's an object I use to can easily add modifiers to any task, in this case, I can tell grunt to not open a new tab with --silent.

  var flags = {
    silent: grunt.option('silent')
    //...
  }

You might as well just remove it and replace it with true if you want the default behavior. My bad for leaving it there, it escaped my eyes

stereokai commented Aug 21, 2014

@hpowers

I keep the following in my Gruntfiles, it's an object I use to can easily add modifiers to any task, in this case, I can tell grunt to not open a new tab with --silent.

  var flags = {
    silent: grunt.option('silent')
    //...
  }

You might as well just remove it and replace it with true if you want the default behavior. My bad for leaving it there, it escaped my eyes

@razvan-tudosa

This comment has been minimized.

Show comment
Hide comment
@razvan-tudosa

razvan-tudosa Sep 8, 2014

If I have sub-routes like http://localhost:9000/route/subroute it doesn't load my css anymore.

It should look in http://localhost:9000/styles/main.css, instead it looks in http://localhost:9000/route/styles/main.css

I think it could be solved with some clever RegEx but I don't have the necessary skills. Can anyone solve this?

LE: Turns out I had my <base href="/"> declared after my css links. As soon as I put the base href before those, everything was ok.

razvan-tudosa commented Sep 8, 2014

If I have sub-routes like http://localhost:9000/route/subroute it doesn't load my css anymore.

It should look in http://localhost:9000/styles/main.css, instead it looks in http://localhost:9000/route/styles/main.css

I think it could be solved with some clever RegEx but I don't have the necessary skills. Can anyone solve this?

LE: Turns out I had my <base href="/"> declared after my css links. As soon as I put the base href before those, everything was ok.

@razvan-tudosa

This comment has been minimized.

Show comment
Hide comment
@razvan-tudosa

razvan-tudosa Sep 25, 2014

After introducing the connect-modrewrite middleware I realised that grunt build task becomes obsolete as now my application is dependent of a node server, it's not a simple Javascript app anymore, so running the project from /dist wouldn't do much.

How would/did you guys handle this?

razvan-tudosa commented Sep 25, 2014

After introducing the connect-modrewrite middleware I realised that grunt build task becomes obsolete as now my application is dependent of a node server, it's not a simple Javascript app anymore, so running the project from /dist wouldn't do much.

How would/did you guys handle this?

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Sep 25, 2014

now my application is dependent of a node server

@razvan-tudosa What do you mean? Node is used by default to serve your app, whether from app or from dist

stereokai commented Sep 25, 2014

now my application is dependent of a node server

@razvan-tudosa What do you mean? Node is used by default to serve your app, whether from app or from dist

@razvan-tudosa

This comment has been minimized.

Show comment
Hide comment
@razvan-tudosa

razvan-tudosa Sep 25, 2014

Looks like I was not aware of that fact, I was thinking that after I run grunt build I would take all of the files and folders under /dist and put them on a server as static resources.

I'm trying to figure out a deploy workflow, can you tell me what exactly I have to do in order to make grunt run the project from /dist ?

LE: Looks like grunt serve:dist should do this.

Thanks @stereokai for opening my mind!

razvan-tudosa commented Sep 25, 2014

Looks like I was not aware of that fact, I was thinking that after I run grunt build I would take all of the files and folders under /dist and put them on a server as static resources.

I'm trying to figure out a deploy workflow, can you tell me what exactly I have to do in order to make grunt run the project from /dist ?

LE: Looks like grunt serve:dist should do this.

Thanks @stereokai for opening my mind!

@stereokai

This comment has been minimized.

Show comment
Hide comment
@stereokai

stereokai Sep 25, 2014

There's no deploy method set up with this generator. However, it would be easy to set up using a plugin such as grunt-ftp-deploy or git-directory-deploy.

To serve (locally) your project in it's built state, use grunt serve:dist

stereokai commented Sep 25, 2014

There's no deploy method set up with this generator. However, it would be easy to set up using a plugin such as grunt-ftp-deploy or git-directory-deploy.

To serve (locally) your project in it's built state, use grunt serve:dist

@szimek

This comment has been minimized.

Show comment
Hide comment
@szimek

szimek Sep 25, 2014

@razvan-tudosa You don't need Node.js server to run production version of your app. We're using e.g. Apache to serve our app and upload contents of /dist folder using SFTP. If you're using html5mode, you just have to add rewrite rules (in the generator it's handled by connect-modrewrite middleware) to always serve index.html regardless of the actual URL (e.g. /my/awesome/page), except for static resources like images, CSS, JS, fonts etc. What server you actually use is up to you.

szimek commented Sep 25, 2014

@razvan-tudosa You don't need Node.js server to run production version of your app. We're using e.g. Apache to serve our app and upload contents of /dist folder using SFTP. If you're using html5mode, you just have to add rewrite rules (in the generator it's handled by connect-modrewrite middleware) to always serve index.html regardless of the actual URL (e.g. /my/awesome/page), except for static resources like images, CSS, JS, fonts etc. What server you actually use is up to you.

@gsc-leticia

This comment has been minimized.

Show comment
Hide comment
@gsc-leticia

gsc-leticia Nov 19, 2014

For latest version this is what works (lines to be added to grunt config file): http://stackoverflow.com/questions/24283653/angularjs-html5mode-using-grunt-connect-grunt-0-4-5

gsc-leticia commented Nov 19, 2014

For latest version this is what works (lines to be added to grunt config file): http://stackoverflow.com/questions/24283653/angularjs-html5mode-using-grunt-connect-grunt-0-4-5

@evansque

This comment has been minimized.

Show comment
Hide comment
@evansque

evansque Dec 26, 2014

I still have an issue with dist directory after build, I get 404 on the asset files.
I use yo --v 1.1.2 and angular-generator --v 0.10.0

evansque commented Dec 26, 2014

I still have an issue with dist directory after build, I get 404 on the asset files.
I use yo --v 1.1.2 and angular-generator --v 0.10.0

@eddiemonge

This comment has been minimized.

Show comment
Hide comment
@eddiemonge

eddiemonge Dec 30, 2014

Member

thats an old yo version. please open a new issue if upgrading doesnt fix it.

Member

eddiemonge commented Dec 30, 2014

thats an old yo version. please open a new issue if upgrading doesnt fix it.

@kunalnagar

This comment has been minimized.

Show comment
Hide comment
@kunalnagar

kunalnagar Jan 24, 2015

For anyone who is trying to deploy the dist directory to github pages with a custom domain, you get a 404 when you refresh a link. Here is a clever workaround for that. It involves copying everything in your index.html over to 404.html :)

kunalnagar commented Jan 24, 2015

For anyone who is trying to deploy the dist directory to github pages with a custom domain, you get a 404 when you refresh a link. Here is a clever workaround for that. It involves copying everything in your index.html over to 404.html :)

@JahlomP

This comment has been minimized.

Show comment
Hide comment
@JahlomP

JahlomP May 7, 2016

This should work for livereload

livereload: {
    options: {
    open: true,
    middleware: function (connect, options) {
          var middlewares = [];
          middlewares.push(modRewrite(['^[^\\.]*$ /index.html [L]'])); //Matches everything that does not contain a '.' (period)
          middlewares.push(connect.static('.tmp'))
          middlewares.push(connect().use(
            '/bower_components',
            connect.static('./bower_components')))
          middlewares.push(connect().use(
            '/app/styles',
            connect.static('./app/styles')))
          middlewares.push(connect.static(appConfig.app))
          options.base.forEach(function(base) {
              middlewares.push(connect.static(base));
          });
          return middlewares;

      },
    }
  }

JahlomP commented May 7, 2016

This should work for livereload

livereload: {
    options: {
    open: true,
    middleware: function (connect, options) {
          var middlewares = [];
          middlewares.push(modRewrite(['^[^\\.]*$ /index.html [L]'])); //Matches everything that does not contain a '.' (period)
          middlewares.push(connect.static('.tmp'))
          middlewares.push(connect().use(
            '/bower_components',
            connect.static('./bower_components')))
          middlewares.push(connect().use(
            '/app/styles',
            connect.static('./app/styles')))
          middlewares.push(connect.static(appConfig.app))
          options.base.forEach(function(base) {
              middlewares.push(connect.static(base));
          });
          return middlewares;

      },
    }
  }
@nitin19srivastava

This comment has been minimized.

Show comment
Hide comment
@nitin19srivastava

nitin19srivastava Feb 25, 2017

Adding : base href="/" with opening less than and closing greater than sign in index.html head section.

Adding : $locationProvider.html5Mode(true).hashPrefix(''); in app.js route section.

installing : npm install connect-modrewrite --save : through command prompt inside project root folder.

in gruntfile.js add : var modRewrite=require('connect-modrewrite');

and

in livereload section:

pass "options" as second parameter, comment the original return code and replace with the given one.

var middlewares = [];
middlewares.push(modRewrite(['^[^\.]*$ /index.html [L]'])); //Matches everything that does not contain a '.' (period)
middlewares.push(connect.static('.tmp'))
middlewares.push(connect().use(
'/bower_components',
connect.static('./bower_components')))
middlewares.push(connect().use(
'/app/styles',
connect.static('./app/styles')))
middlewares.push(connect.static(appConfig.app))
options.base.forEach(function(base) {
middlewares.push(connect.static(base));
});
return middlewares;

stop the server and start again.

Works fine for me.
app
commandprompt
gruntfile js-1
gruntfile js-2
index

nitin19srivastava commented Feb 25, 2017

Adding : base href="/" with opening less than and closing greater than sign in index.html head section.

Adding : $locationProvider.html5Mode(true).hashPrefix(''); in app.js route section.

installing : npm install connect-modrewrite --save : through command prompt inside project root folder.

in gruntfile.js add : var modRewrite=require('connect-modrewrite');

and

in livereload section:

pass "options" as second parameter, comment the original return code and replace with the given one.

var middlewares = [];
middlewares.push(modRewrite(['^[^\.]*$ /index.html [L]'])); //Matches everything that does not contain a '.' (period)
middlewares.push(connect.static('.tmp'))
middlewares.push(connect().use(
'/bower_components',
connect.static('./bower_components')))
middlewares.push(connect().use(
'/app/styles',
connect.static('./app/styles')))
middlewares.push(connect.static(appConfig.app))
options.base.forEach(function(base) {
middlewares.push(connect.static(base));
});
return middlewares;

stop the server and start again.

Works fine for me.
app
commandprompt
gruntfile js-1
gruntfile js-2
index

TomaszBoron pushed a commit to dominik23213/frontaz that referenced this issue Mar 16, 2017

Setup gruntfile for livereload in html5mode.
see this: yeoman/generator-angular#433
and
install this:
npm install grunt-karma karma karma-phantomjs-launcher karma-jasmine jasmine-core phantomjs-prebuilt --save-dev
@vsajja

This comment has been minimized.

Show comment
Hide comment
@vsajja

vsajja Apr 14, 2017

"I followed this approach but still have issues with /:params. The url to load main.css is getting prefixed with current url instead of base path. It is like localhost:9000/mypage/styles/main.css (failed to load 404) instead of localhost:9000/styles/main.css"

Any ideas how to get livereload working with URL parameters?

vsajja commented Apr 14, 2017

"I followed this approach but still have issues with /:params. The url to load main.css is getting prefixed with current url instead of base path. It is like localhost:9000/mypage/styles/main.css (failed to load 404) instead of localhost:9000/styles/main.css"

Any ideas how to get livereload working with URL parameters?

@nitin19srivastava

This comment has been minimized.

Show comment
Hide comment
@nitin19srivastava

nitin19srivastava Apr 14, 2017

nitin19srivastava commented Apr 14, 2017

@nitin19srivastava

This comment has been minimized.

Show comment
Hide comment
@nitin19srivastava

nitin19srivastava Apr 17, 2017

Put your Css file in styles folder, and in html put your custom css link tag after main.css link tag

nitin19srivastava commented Apr 17, 2017

Put your Css file in styles folder, and in html put your custom css link tag after main.css link tag

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.