Open jjt opened 10 years ago
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 https://github.com/yeoman/generator-angular/issues/432.
Problem 1 is an ongoing issue that probably does need to be addressed at some point.
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?
:+1:
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>
My solution for this is as follows:
$locationProvider.html5Mode(true);
<base href="/">
in <head>
tag
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)
@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 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 Thanks! I removed the require('connect-livereload')()
line and everything worked.
Yeah, my snippet targeted v0.4.0. Thanks for the updated code!
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()
@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 (https://github.com/gruntjs/grunt-contrib-connect/issues/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.
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.
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.
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.
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); }));
}
}
}
It will be hard to make it any shorter than this :)
I don't think I want it any shorter! As is, this almost feels too "clever".
started thinking about how to implement this. added a note in https://github.com/eddiemonge/generator-angular-api
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)
assetSrcPrefix = "/"
assetSrcPrefix
to all asset paths (js, css) in various files (wherever src="script/..."
or href="styles/..."
would be output)assetBasePathProd
to all build targets (ex. build:js <%= assetBasePathProd %>scripts...
)connect-rewrite
in package.json
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
.
just dropping a +1 here for "have enough interest in order to justify adding it" !
Thanks guys!
+1, this would be fantastic to have
+1
+1 - would be fantastic to have it included
+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 .
Looks wonderful. Yes please
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.
+1 (v0.6.0)
+1
Excellent, not working again.
:+1: really want to see this done.
encounter this issue and resolve it manually using this discussion as the guide.. +1
:dart: working with the latest angular-generator :+1:
Not for latest version. Grunt file is heavily modified unlike in that commit
@zishe the @jjt solution worked for me with generator-angular 0.7.1
, the latest stable version available.
Can you show your solution, because it's not working for me with just created app with the same version.
Yes, https://gist.github.com/lifeisfoo/9570280 I hope this help.
It works as long as our path is flat ... e.g. mydir
works, whereas mydir/mysubdir
doesn't work anymore – until I manually add a <base href="/">
tag into the head.
I can confirm what @peta says above. I created a basic app generated with generator-angular version 0.7.1 (running on Linux Mint, Node 0.10.26, npm 1.4.3) and followed the suggested steps in https://github.com/yeoman/generator-angular/issues/433#issuecomment-28838283 with the Gruntfile.sj addition of the middleware in https://github.com/yeoman/generator-angular/issues/433#issuecomment-28554867. It works only for flat paths. For example, I can access /level1, but if I try to access /level1/:id (say /level1/12) I get the following error in the console: GET http://localhost:9000/level1/views/level1-details.html 404 (Not Found) And if I try to access /level1/:id/:foo (say /level1/13/foo) I get: GET http://localhost:9000/level1/13/views/level1-details-foo.html 404 (Not Found)
If I add <base href="/">
, in addition to the steps followed as desribed, then it all seems to work.
A few things:
<base href="/">
is not ideal for reasons previously raisedSo, here's my latest solution (a small mod of @jjt's snippet). It accounts for both *nix and Windows and it allows rewriting/aliasing directories too (to support v0.9.5 config layout). And it supports sourcemaps too. I have tested it with the latest version (0.9.5) and latest connect-modrewrite (0.7.6). Just replace your connect config object in your Gruntfile with the following. Of course it requires <base href="/">
.
// The actual grunt server settings
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: 'localhost',
livereload: 35729,
// 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,
middleware = [require('connect-modrewrite')(['!(\\..+)$ / [L]'])]
.concat(optBase.map(function (path) {
if (path.indexOf('rewrite|') === -1) {
return connect.static(path);
} else {
path = path.replace(/\\/g, '/').split('|');
return connect().use(path[1], connect.static(path[2]))
}
}));
return middleware;
}
},
livereload: {
options: {
open: true,
base: [
'.tmp',
'rewrite|/bower_components|./bower_components',
'rewrite|/app/styles|./app/styles', // for sourcemaps
'<%= yeoman.app %>'
]
}
},
test: {
options: {
port: 9001,
base: [
'.tmp',
'test',
'rewrite|/bower_components|./bower_components',
'rewrite|/app/styles|./app/styles',
'<%= yeoman.app %>'
]
}
},
dist: {
options: {
base: '<%= yeoman.dist %>'
}
}
},
@stereokai's snippet worked for me after scratching my head on this problem for quite a while. Thanks!
@bjacobel Glad I could help!
Well @stereokai last snippet almost works for me. But I'm using ui-router and its adding a trailing '/' which makes the URL not valid to the state, so it brings me back to home. Almost there just need to get rid of that '/'.
I feel like this moving the bower_components folder out of app is kinda a PITA.
Sadly, I think this may be chrome adding the slash when the page is reloaded or something. I tried this, but did nothing for me. Any ideas?
middleware: function (connect, options) {
var optBase = (typeof options.base === 'string') ? [options.base] : options.base,
middleware = [require('connect-modrewrite')(['!(\\..+)$ / [L]'])]
.concat(optBase.map(function (path) {
if (path.indexOf('rewrite|') === -1) {
if (path.charAt(path.length-1) === '/') path = path.slice(0,-1);
return connect.static(path);
} else {
path = path.replace(/\\/g, '/').split('|');
return connect().use(path[1], connect.static(path[2]))
}
}));
return middleware;
}
May I bring into discussion the reasoning behind moving bower_components
out of the app directory?
I can relate to the notion that these are dependencies that are installed in the same manner as node_modules
, but at the same time they are also browser assets that are served with the rest of the app contents, and node_modules
aren't. (My impression is that the common setup is having bower_components
inside app).
Well, I was directed to a fix for my trailing slash issue that was the last thing preventing this modrewrite livereload/refresh fix to work and angular ui-route. I simple have added a trailing slash to all my routes... and I added a little rule to append one if there isn't:
$urlRouterProvider
.rule(function ($injector, $location) {
// This rule forces all URLs to have a trailing '/'
var path = $location.url();
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1)
return;
if (path.indexOf('?') > -1)
return path.replace('?', '/?');
return path + '/';
})
Finally I can say my dev server handles reload/refreshes correctly, definitely a big bonus.
@bobmash Did you have to use your middleware
snippet? Just for reference.
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:
npm install connect-modrewrite --save-dev
. At the time of writing I'm using 0.7.7 and grunt-contrib-connect 0.8.0 //....
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 %>'
}
}
}
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 You shouldn't need to manually include the fonts, they should already be served (they are automatically included via bower,json)
@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 Thanks for sharing. I'm not using Less, but others who do might find it useful for sure.
Anyone who wants to use html5mode with generator-angular has three problems to solve after
yo angular
is used to create a project:/
results in 404 (index.html is only served from /)/
index.html
use relativescripts/
path, and the builtindex.html
has relative script tags, as aboveI 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 insteadthis, as of 0.6.0-rc1From 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:
<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.<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./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?