How can I tell grunt-usemin to ignore Django's static template tags?
Asked Answered
V

2

8

I'm building a Django app that serves a single page Angular app.

Django serve's Angular's index.html which has the following usemin script block:

<!-- build:js scripts/scripts.js -->
<script src="{% static "scripts/app.js" %}"></script>
<script src="{% static "scripts/controllers/main.js" %}"></script>
<script src="{% static "scripts/controllers/stats.js" %}"></script>
<script src="{% static "scripts/directives/highchart.js" %}"></script>
<!-- endbuild -->

Usemin tries to find "{% static "scripts/app.js" %}" on the filesystem and of course fails because what it should really be trying to find is "scripts/app.js".

Has anyone seen a workaround for this?

Vogele answered 17/8, 2013 at 15:42 Comment(5)
Did you build the site with grunt build, there should not be a usemin block served to the client, this is only for grunt and has nothing to do with the backend (afaik).Decent
I'm trying to take advantage of Django's reverse url lookup for the static url, so I'm serving Angular's index.html from a Django route, which allows me to use Django template tags in index.html. I've since abandoned that and will now access index.html from the static url, rather than a proper Django route. I'll just bite the bullet and if my static file url ever changes I'll update index.html by hand.Vogele
@davemckenna01 did you find anything new on this since August when you originally asked? I'm currently using grunt/yeoman with Angular and integrating the workflow with django is slowing me down quite a bit.Montfort
@kvseelbach I am also having the same problem. Did you find a workaround?Enharmonic
@Enharmonic I wanted to keep using Grunt and the generators so I edited the config and installed grunt-text-replace and set up a few search/replacements. replace: { example: { src: ['<%= yeoman.dist %>/*.html'], overwrite: true, replacements: [{ from: '<script src="scripts', to: '<script src="/static/mdb/scripts' }] } }Montfort
E
9

Alright, I think I have a workaround. This is assuming you want the built file to also point to the static url for the built assets.

This will require you to define a STATIC_URL on your view context (instead of using src="{% static 'foo/bar.js' %}" you'll use src="{{STATIC_URL}}foo/bar.js"). I couldn't get {% static %} to work without hacking the grunt-usemin source.

So using your example, this:

<!-- build:js {{STATIC_URL}}scripts/scripts.js -->
<script src="{{STATIC_URL}}scripts/app.js"></script>
<script src="{{STATIC_URL}}scripts/controllers/main.js"></script>
<script src="{{STATIC_URL}}scripts/controllers/stats.js"></script>
<script src="{{STATIC_URL}}scripts/directives/highchart.js"></script>
<!-- endbuild -->

Compiles down to:

<script src="{{STATIC_URL}}scripts/scripts.js"></script>

In order to achieve this, I had to add the following grunt configurations (in Gruntfile.js):

// custom createConfig script for replacing Django {{STATIC_URL}} references
// when building config for concat and cssmin
var path = require('path');
function createDjangoStaticConcatConfig(context, block) {
  var cfg = {files: []};
  var staticPattern = /\{\{\s*STATIC_URL\s*\}\}/;

  block.dest = block.dest.replace(staticPattern, '');
  var outfile = path.join(context.outDir, block.dest);

  // Depending whether or not we're the last of the step we're not going to output the same thing
  var files = {
    dest: outfile,
    src: []
  };
  context.inFiles.forEach(function(f) {
    files.src.push(path.join(context.inDir, f.replace(staticPattern, '')));
  });
  cfg.files.push(files);
  context.outFiles = [block.dest];
  return cfg;
}


grunt.initConfig({
    /*...*/

    // add a preprocessor to modify the concat config to parse out {{STATIC_URL}} using the above method
    useminPrepare: {
      html: 'app/index.html',
      options: {
        dest: 'dist',
        flow: {
          steps: {
            js: [
              {
                name: 'concat',
                createConfig: createDjangoStaticConcatConfig
              },
              'uglifyjs'
            ],
            // also apply it to css files
            css: [
              {
                name: 'cssmin',
                createConfig: createDjangoStaticConcatConfig
              }
            ]
          },
          // this property is necessary
          post: {}
        }
      }
    },

    // add a pattern to parse out the actual filename and remove the {{STATIC_URL}} bit
    usemin: {
      html: ['dist/{,*/}*.html'],
      css: ['dist/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['dist'],
        patterns: {
          html: [[/\{\{\s*STATIC_URL\s*\}\}([^'"]*)["']/mg, 'django static']]
        }
      }
    },


    // if you are using bower, also include the jsPattern to automatically 
    // insert {{STATIC_URL}} when inserting js files
    'bower-install': {
      app: {
        html: 'app/index.html',
        jsPattern: '<script type="text/javascript" src="{{STATIC_URL}}{{filePath}}"></script>'
      }
    }
});
Enharmonic answered 20/1, 2014 at 0:14 Comment(4)
great work, but the output doesn't produce the {{STATIC_URL}} prefixRippy
Hey thanks, this fix my problem with php. for people who have a problem with this, the 'post: {}' property is neccesaryDecorticate
@Decorticate yes, it is. I'll make a note in there so it's more obvious. Thanks!Enharmonic
@mouse, I had the same problem of no {{STATIC_URL}} - I had to add a blockReplacements section, returning something like return '<script src="{{STATIC_URL}}path/'+block.dest+'"></script>';Ammonic
T
3

(I keept finding this question when googling for a solution, so I just share the solution i found here so that others like me can find it.)

I just discovered a grunt plugin called grunt-bridge that can wrap static files in a html template with the static tag. The documentation is not great but I figured how to get it to work for my project.

If you have scripts like this:

<!-- build:js({.tmp,app}) /scripts/scripts.js -->
    <script src="scripts/app.js"></script>
    <script src="scripts/util/util.js"></script>
    ...

And the final output it to output something like this:

<script src="{% static 'adjangoappname/scripts/94223d51.scripts.js' %}"></script>

Install gruntbridge with npm install grunt-bridge --save-dev and add a config like this in the grunt.initConfig of the Gruntfile.js:

bridge: {
  distBaseHtml: {
      options: {
          pattern: '{% static \'adjangoappname{path}\' %}',
          html: '<%= yeoman.minifiedDir %>/index.html', dest: '<%= yeoman.templateDir %>/index.html'
      }
  },
  distIndexHtml: {
      options: {
          pattern: '{% static \'adjangoappname{path}\' %}',
          html: '<%= yeoman.minifiedDir %>/index.html', dest: '<%= yeoman.templateDir %>/index.html'
      }
  },
  // ... etc for each html file you want to modify
},

Where <%= yeoman.minifiedDir %> is the output directory of the final minified html files, and <%= yeoman.minifiedDir %> is the destination template directory. Replace adjangoappname with the name of your django app or whatever directory prefix you want to have.

Then add grunt-bridge last in the registerTask list like this:

grunt.registerTask('build', [
    //...
    'htmlmin',
    'bridge'
]);

(I think it's possible to make the config more compact, but this is the first method I discovered that worked)

Tindle answered 20/6, 2014 at 6:57 Comment(1)
The grunt-bridge plugin is great. I tested it with yeoman-webapp. I needed to remove 'htmlmin' from the 'build' task because grunt-bridge does not like the missing quotes 'htmlmin' produces.Flare

© 2022 - 2024 — McMap. All rights reserved.