grunt-usemin
grunt-usemin copied to clipboard
Nested HTML files issue with relative paths to (CSS) assets
I am experiencing an issue with usemin where paths are not quite working as expected. In this example, it is regarding CSS references within nested HTML files. Here is an abbreviated overview of my project directory / file structure:
C:\grunt_project | bower.json | Gruntfile.js | package.json | +---.tmp | ---styles | | main.css | | | ---elements | greeting.css | +---app | | index.html | | | +---elements | | greeting.html | | | +---images | +---scripts | | main.js | | | ---styles | | main.scss | | | ---elements | greeting.scss | +---bower_components | +---build | | index.html | | | +---elements | | greeting.html | | | +---scripts | | 73070f31.vendor.js | | b6c3df09.main.js | | | ---styles | 4e0905d0.main.css | ---elements | d9d6f0c6.greeting.css
Everything works properly for /app/index.html:
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>Polymer WebApp3</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="styles/main.css">
<!-- Place your HTML imports here -->
<!-- endbuild -->
<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
<script src="../bower_components/platform/platform.js"></script>
<!-- endbower -->
<!-- endbuild -->
<link rel="import" href="elements/greeting.html">
</head>
<body unresolved>
<div class="hero-unit">
<polymer-greeting></polymer-greeting>
<p>Polymer greeting test</p>
<script>
document.addEventListener('WebComponentsReady', function () {
// Perform some behaviour
});
</script>
</div>
<!-- build:js({app,.tmp}) scripts/main.js -->
<script src="scripts/main.js"></script>
<!-- endbuild -->
</body>
</html>
The problem occurs with the source /app/elements/greeting.html:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="polymer-greeting" attributes="">
<template>
<style>
/* styles for the custom element itself - lowest specificity */
:host {
display: block;
}
/*
style if an ancestor has the different class
:host(.different) { }
*/
</style>
<!-- build:css(.tmp) styles/elements/greeting.css -->
<link rel="stylesheet" href="styles/elements/greeting.css">
<!-- endbuild -->
<h1>{{ greeting }}, {{ greeting }}!</h1>
<span>Update text to change the greeting.</span>
<input type="text" value="{{ greeting }}">
</template>
<script>
Polymer('polymer-greeting', {
greeting: '\'Allo'
});
</script>
</polymer-element>
You'll notice that the href attribute value pointing to greeting.css in the link element is incorrect. The correct value is ../styles/elements/greeting.css, however, the only way I have been able to get usemin / rev to run successfully to completion (meaning all CSS assets get concat-ed, minified, and revved into the build directory) is to use the incorrect path.
The resulting /build/elements/greeting.html is:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="polymer-greeting" attributes="">
<template>
<style>
/* styles for the custom element itself - lowest specificity */
:host {
display: block;
}
/*
style if an ancestor has the different class
:host(.different) { }
*/
</style>
<link rel="stylesheet" href="styles/elements/d9d6f0c6.greeting.css">
<h1>{{ greeting }}, {{ greeting }}!</h1>
<span>Update text to change the greeting.</span>
<input type="text" value="{{ greeting }}">
</template>
<script>
Polymer('polymer-greeting', {
greeting: '\'Allo'
});
</script>
</polymer-element>
This ultimately causes the final step of my build process, grunt-vulcanize, to fail. I've tried using various path combinations and even defining a custom blockReplacements type/function to add the relative path in (side note: this function would never get called, possible bug?), all to no avail. Maybe what I am trying to accomplish is not possible?
Here is my Grunt package.json:
{
"name": "polymer-app3",
"version": "0.0.0",
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-copy": "~0.5.0",
"grunt-contrib-concat": "^0.3.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-sass": "^0.11.0",
"grunt-contrib-jshint": "^0.9.2",
"grunt-contrib-cssmin": "^0.9.0",
"grunt-contrib-connect": "^0.7.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-htmlmin": "^0.2.0",
"grunt-bower-install": "^1.4.1",
"grunt-contrib-imagemin": "^0.6.1",
"grunt-contrib-watch": "~0.6.1",
"grunt-rev": "~0.1.0",
"grunt-autoprefixer": "^0.7.6",
"grunt-usemin": "^2.1.1",
"grunt-mocha": "~0.4.10",
"grunt-newer": "~0.7.0",
"grunt-svgmin": "~0.4.0",
"grunt-concurrent": "~0.5.0",
"load-grunt-tasks": "^0.4.0",
"time-grunt": "~0.3.1",
"jshint-stylish": "^0.1.5",
"grunt-vulcanize": "^0.3.0"
},
"engines": {
"node": ">=0.10.0"
}
}
Here's is my Gruntfile.js:
// Generated on 2014-07-08 using generator-webapp 0.4.9
/* jshint -W106 */ //Disable JSHint warning for non-camelCase compliance.
/* jshint -W087 */ //Disable JSHint warning for debugger statements.
'use strict';
// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'
module.exports = function (grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);
// Configurable paths
var config = {
app: 'app',
build: 'build',
dist: 'dist'
};
// Define the configuration for all the tasks
grunt.initConfig({
// Project settings
config: config,
// Watches files for changes and runs tasks based on the changed files
watch: {
bower: {
files: ['bower.json'],
tasks: ['bowerInstall']
},
js: {
files: ['<%= config.app %>/scripts/{,*/}*.js'],
tasks: ['jshint'],
options: {
livereload: true
}
},
jstest: {
files: ['test/spec/{,*/}*.js'],
tasks: ['test:watch']
},
gruntfile: {
files: ['Gruntfile.js']
},
sass: {
files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'],
tasks: ['sass:server', 'autoprefixer']
},
sass_polymer: {
files: ['<%= config.app %>/styles/elements{,*/}*.{scss,sass}'],
tasks: ['sass:server', 'autoprefixer'],
options: {
livereload: true
}
},
styles: {
files: ['<%= config.app %>/styles/{,*/}*.css'],
tasks: ['newer:copy:styles', 'autoprefixer']
},
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'<%= config.app %>/{,*/}*.html',
'.tmp/styles/{,*/}*.css',
'<%= config.app %>/images/{,*/}*'
]
}
},
// The actual grunt server settings
connect: {
options: {
port: 9000,
open: true,
livereload: 35729,
// Change this to '0.0.0.0' to access the server from outside
hostname: 'localhost'
},
livereload: {
options: {
middleware: function (connect) {
return [
connect.static('.tmp'),
connect().use('/bower_components', connect.static('./bower_components')),
connect.static(config.app)
];
}
}
},
test: {
options: {
open: false,
port: 9001,
middleware: function (connect) {
return [
connect.static('.tmp'),
connect.static('test'),
connect().use('/bower_components', connect.static('./bower_components')),
connect.static(config.app)
];
}
}
},
build: {
options: {
middleware: function (connect) {
return [
connect.static('.tmp'),
connect().use('/bower_components', connect.static('./bower_components')),
connect.static(config.build)
];
},
livereload: false
}
},
dist: {
options: {
base: '<%= config.dist %>',
livereload: false
}
}
},
// Empties folders to start fresh
clean: {
default: {
files: [{
dot: true,
src: [
'.tmp',
'<%= config.build %>/*',
'!<%= config.build %>/.git*',
'<%= config.dist %>/*',
'!<%= config.dist %>/.git*'
]
}]
},
server: '.tmp'
},
// Make sure code styles are up to par and there are no obvious mistakes
jshint: {
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
},
all: [
'Gruntfile.js',
'<%= config.app %>/scripts/{,*/}*.js',
'!<%= config.app %>/scripts/vendor/*',
'test/spec/{,*/}*.js'
]
},
// Mocha testing framework configuration options
mocha: {
all: {
options: {
run: true,
urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
}
}
},
// Compiles Sass to CSS and generates necessary files if requested
sass: {
options: {
includePaths: [
'bower_components'
]
},
dist: {
files: [{
expand: true,
cwd: '<%= config.app %>/styles',
src: ['{,*/}*.{scss,sass}'],
dest: '.tmp/styles',
ext: '.css'
}]
},
server: {
files: [{
expand: true,
cwd: '<%= config.app %>/styles',
src: ['{,*/}*.{scss,sass}'],
dest: '.tmp/styles',
ext: '.css'
}]
}
},
// Add vendor prefixed styles
autoprefixer: {
options: {
browsers: ['last 1 version']
},
dist: {
files: [{
expand: true,
cwd: '.tmp/styles/',
src: '{,*/}*.css',
dest: '.tmp/styles/'
}]
}
},
// Automatically inject Bower components into the HTML file
bowerInstall: {
app: {
src: ['<%= config.app %>/index.html'],
exclude: ['bower_components/bootstrap-sass-official/vendor/assets/javascripts/bootstrap.js']
},
sass: {
src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}']
}
},
// Renames files for browser caching purposes
rev: {
build: {
files: {
src: [
'<%= config.build %>/scripts/{,*/}*.js',
'<%= config.build %>/styles/{,*/}*.css',
'<%= config.build %>/images/{,*/}*.*',
'<%= config.build %>/styles/fonts/{,*/}*.*',
'<%= config.build %>/*.{ico,png}'
]
}
}
},
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
options: {
dest: '<%= config.build %>'
},
html: '<%= config.app %>/{,*/}*.html'
},
// Performs rewrites based on rev and the useminPrepare configuration
usemin: {
options: {
assetsDirs: ['<%= config.build %>', '<%= config.build %>/images']
},
html: ['<%= config.build %>/{,*/}*.html'],
css: ['<%= config.build %>/styles/{,*/}*.css']
},
// The following *-min tasks produce minified files in the dist folder
imagemin: {
build: {
files: [{
expand: true,
cwd: '<%= config.app %>/images',
src: '{,*/}*.{gif,jpeg,jpg,png}',
dest: '<%= config.build %>/images'
}]
}
},
svgmin: {
build: {
files: [{
expand: true,
cwd: '<%= config.app %>/images',
src: '{,*/}*.svg',
dest: '<%= config.build %>/images'
}]
}
},
htmlmin: {
build: {
options: {
/*
// https://github.com/yeoman/grunt-usemin/issues/44
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeCommentsFromCDATA: true,
removeEmptyAttributes: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
useShortDoctype: true*/
},
files: [{
expand: true,
cwd: '<%= config.build %>',
src: '{,*/}*.html',
dest: '<%= config.build %>'
}]
}
},
//Vulcanize the polymer application into a consolidated file (or files)
vulcanize: {
default: {
options: {
inline: true
},
files: {
'<%= config.dist %>/index.html': ['<%= config.build %>/index.html']
}
}
},
// Copies remaining files to places other tasks can use
copy: {
build: {
files: [{
expand: true,
dot: true,
cwd: '<%= config.app %>',
dest: '<%= config.build %>',
src: [
'*.{ico,png,txt}',
'.htaccess',
'images/{,*/}*.webp',
'{,*/}*.html',
'styles/fonts/{,*/}*.*'
]
}]
},
styles: {
expand: true,
dot: true,
cwd: '<%= config.app %>/styles',
dest: '.tmp/styles/',
src: '{,*/}*.css'
}
},
// Run some tasks in parallel to speed up build process
concurrent: {
server: [
'sass:server',
'copy:styles'
],
test: [
'copy:styles'
],
build: [
'sass',
'copy:styles',
'imagemin',
'svgmin'
]
}
});
grunt.registerTask('serve', function (target) {
if (target === 'build') {
return grunt.task.run(['build', 'connect:build:keepalive']);
}
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
grunt.registerTask('server', function (target) {
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
grunt.task.run([target ? ('serve:' + target) : 'serve']);
});
grunt.registerTask('test', function (target) {
if (target !== 'watch') {
grunt.task.run([
'clean:server',
'concurrent:test',
'autoprefixer'
]);
}
grunt.task.run([
'connect:test',
'mocha'
]);
});
//This will create the "build" application and also generate the vulcanized "dist" application.
grunt.registerTask('build', [
'clean',
'useminPrepare',
'concurrent:build',
'autoprefixer',
'concat',
'cssmin',
'uglify',
'copy:build',
'rev',
'usemin',
'htmlmin',
'vulcanize'
]);
grunt.registerTask('default', [
'newer:jshint',
'test',
'build'
]);
};
Any insight into this would be greatly appreciated.
Quick update to this issue. I was able to resolve it by updating grunt-usemin/lib/configwriter.js to the version from @buddhike: f966221f4d58bbcf63b4a4b2f3a6373817295ecf (see also #298) and then update my app/elements/greeting.html file as follows:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="polymer-greeting" attributes="">
<template>
<style>
/* styles for the custom element itself - lowest specificity */
:host {
display: block;
}
/*
style if an ancestor has the different class
:host(.different) { }
*/
</style>
<!-- build:css(.tmp/fakedir) ../styles/elements/greeting.css -->
<link rel="stylesheet" href="../styles/elements/greeting.css">
<!-- endbuild -->
<h1>{{ greeting }}, {{ greeting }}!</h1>
<span>Update text to change the greeting.</span>
<input type="text" value="{{ greeting }}">
</template>
<script>
Polymer('polymer-greeting', {
greeting: '\'Allo'
});
</script>
</polymer-element>
I had to trick the configwriter by adding a fakedir in the alternate search path param so that it would properly resolve up one level. It's slightly hack-ish but I'm happy with it as a short-term workaround.
#298 has been closed due to lack of response. I think some issues cover the same thing you need.
Thanks for following up on this @stephanebachelier .
@cookch10 will come back on this issue to reference any linked issue.
Excellent :thumbsup: