yeoman / grunt-usemin

[UNMAINTAINED] Replaces references to non-optimized scripts or stylesheets into a set of HTML files (or any templates/views)
BSD 2-Clause "Simplified" License
1.22k stars 338 forks source link

Nested HTML files issue with relative paths to (CSS) assets #400

Open cookch10 opened 10 years ago

cookch10 commented 10 years ago

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.

cookch10 commented 10 years ago

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.

stephanebachelier commented 9 years ago

298 has been closed due to lack of response.

I think some issues cover the same thing you need.

cookch10 commented 9 years ago

Thanks for following up on this @stephanebachelier .

stephanebachelier commented 9 years ago

@cookch10 will come back on this issue to reference any linked issue.

cookch10 commented 9 years ago

Excellent :thumbsup: