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 339 forks source link

blockReplacements run before filerev linking #444

Open monitorjbl opened 10 years ago

monitorjbl commented 10 years ago

When using filerev before usemin, the output given to a blockReplacements function is the original filename instead of the filerev-adjusted file name (suffixed with a hash). To properly use this option, the blockReplacements functions should be called after the filrev-linking has been done and the hashed filenames are available.

leocwolter commented 10 years ago

In other words: we want to manipulate the final imported assets uris, and not the ones generated before filerev. This way we can support our application at other contexts than root, for example.

monitorjbl commented 10 years ago

For anyone else that is having this issue, here's a workaround. Specify your blockReplacement however you like (in this case, we had to prepend a JSP context root onto the outputted path):

grunt.initConfig({
    config: {
        root: 'src/main/webapp/'
    },

    usemin: {
        html: ['<%= config.root %>/WEB-INF/{jsp,tags}/**/*.{jsp,jspf,tag}'],
        options: {
            blockReplacements: {
                css: function (block) {
                    return '<link rel="stylesheet" href="${contextPath}' + block.dest + '"/>';
                },
                js: function (block) {
                    return '<script src="${contextPath}' + block.dest + '"></script>';
                }
            }
        }
    }    
});

Then, create a custom task that also replaces the filerev map with the prefixed data. Note that you need to stick the prefix after the root path on the filesystem. In our case, it was src/main/webapp/ and it was configured in the config.root property in grunt.initConfig. Your case may be different, but you can use the same string replacement method that I'm using here:

grunt.registerTask('remapFilerev', function(){
    var root = grunt.config().config.root;
    var summary = grunt.filerev.summary;
    var fixed = {};

    for(key in summary){
        if(summary.hasOwnProperty(key)){
            var orig = key.replace(root, root+'${contextPath}/');
            var revved = summary[key].replace(root, root+'${contextPath}/');
            fixed[orig] = revved;
        }
    }

    grunt.filerev.summary = fixed;
});

Finally, insert this task between filerev and usemin in your build task:

grunt.registerTask('build', ['default', 'useminPrepare', 'filerev', 'remapFilerev', 'usemin']);

Really hokey, and it took me a long time to figure out. Hopefully this issue gets fixed so you can just use the blockReplacement method. On the plus side, I did get to learn about the excellent Node debugger.

stephanebachelier commented 9 years ago

@monitorjbl @leocwolter regarding the remapFilerev implementation, are you trying to add a contextPath to each revved assets ?

If this is the case, I think you should rewrite all you revved assets after the usemin process is complete using any grunt plugin like grunt-cdnify.

Waiting for your answer.

monitorjbl commented 9 years ago

The goal was to append the ${contextPath} prefix to all referenced assets. In the application, this would be a JSP variable filled in at runtime with the absolute root path of the server.

Does your suggestion still apply? I'm not that familiar with other Grunt plugins.

stephanebachelier commented 9 years ago

@monitorjbl there are plugins like grunt-cdn or grunt-cdnify. The idea is to run the task after usemin task has completed.

stephanebachelier commented 9 years ago

@monitorjbl I do not want to say that's a better to use another plugin. Your solution to create your own grunt task is good too.

stephanebachelier commented 9 years ago

CC @sindresorhus what do you think about it ? CDN or rewriting assets with a base path/url, might not be usemin priority, but filerev give us a hasmap with all revved assets. Using another grunt plugin should be the way to go IMO, but these plugins seems to have some issues with parsing.

mixxorz commented 9 years ago

I encountered this issue as well while trying to use usemin with filerev in a Django project. I could use something like grunt-cdn but I'd rather not. Django already handles linking to its static assets via the configured settings. Whether the static assets would eventually end up on S3, or on the server's file system should be handled by Django.

Basically, I want my grunt workflow to be agnostic about where the files will eventually end up being hosted. Though, my use case might just be too specific. That is, trying to use usemin for Django templates.

tconroy commented 9 years ago

Ahh I just found this after posting a similar issue. I've attempted to use grunt-cdn but it's not working out for me. Did anyone else come up with a reliable solution? I could definitely see this being a useful feature to incorporate some sort of build prefix.

fodma1 commented 8 years ago

After I spent 1.5 days fixing this problem, I thought I share my solution for the simplest usecase: I wanted to prepend the revved url paths with the cdn base url/the app's base url (for the local devenv).

module.exports = function( grunt ) {

  var options = {
    routes: {
      'static': 'myapp/static',
      'dist': 'myapp/dist',
      'tmp': '.tmp'
    },
    paths: {
      baseTemplateSource: 'myapp/templates/layout/base_src.html',
      baseTemplateTarget: 'myapp/templates/layout/base.html',
      baseTemplateStatic: 'myapp/static/base.html'
    }
  };

  grunt.initConfig({
    options: options,

    usemin: {
      html: ['<%= options.paths.baseTemplateStatic %>'],
      css: ['<%= options.routes.dist %>/css/*.css'],
      options: {
        assetsDirs: ['<%= options.routes.dist %>', '<%= options.routes.dist %>/css'],
        blockReplacements: {
          css: function (block) {
            var originalRevvedDest = grunt.filerev.summary[options.routes.dist + block.dest];
            var customDest = originalRevvedDest.replace(options.routes.dist, '{{ STATIC_BASE_URL }}');
            return '<link rel="stylesheet" href="' + customDest + '">'
          },
          js: function (block) {
            var originalRevvedDest = grunt.filerev.summary[options.routes.dist + block.dest];
            var customDest = originalRevvedDest.replace(options.routes.dist, '{{ STATIC_BASE_URL }}');
            return '<script src="' + customDest + '"></script>';
          }
        }
      }
    }
  });
};

(Note I'm serving my templates with Flask, so {{ STATIC_BASE_URL }} is for jinja2, but can be replaced with url prefixes)

Luckily there's a PR waiting for reviewers: https://github.com/yeoman/grunt-usemin/pull/613