2
votes

I have two tasks when I build with grunt:

A first task preprocess:dist that copies app/scripts/config.js to dist/scripts/config.js and replaces // @echo FOO to BAR.

Then, usemin, that concats all app/scripts/**/*.js to dist/scripts/application.js.

Build task in Gruntfile:

grunt.registerTask('build', [
    'clean:dist',
    'preprocess:dist',
    'useminPrepare',
    ...
    'htmlmin'
    'concat',
    'usemin'
]

My include scripts in index.html:

<!-- build:js({.tmp,dist,app}) scripts/application.js -->
<script src="/scripts/config.js"></script>
<script src="/scripts/other_file.js"></script>
<script src="/scripts/yet_another_file.js"></script>
<!-- endbuild -->

Everything works fine, except that in the concatenated file dist/scripts/application.js, I have the config.js from app/scripts, and not the one from dist/scripts.

I tought that the ({.tmp,dist,app}) after build:js was for specifying where to take the files when concatenating, but still config.js is taken from the wrong directory (app and not dist).

So I end up with the config.js where // @echo VAR not replaced.

How can I tell concat/usemin to take config.js from dist and not app when concatenating ?


Note: One solution would be to have preprocess to put a config.processed.js in app/scripts, and then including this file instead of config.js, so that I don't have to change the way usemin works.

But then I have to put config.processed.js in .gitignore and I have a generated file inside my sources files...

I would prefer having this file generated in .tmp or dist.


Edit: my whole Gruntfile:

// Generated on 2013-10-31 using generator-angular 0.5.1
'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) {
  require('time-grunt')(grunt);
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  grunt.loadNpmTasks('grunt-preprocess');
  grunt.loadNpmTasks('grunt-include-source');

  grunt.initConfig({
    yeoman: {
      // configurable paths
      app: require('./bower.json').appPath || 'app',
      dist: 'dist'
    },
    watch: {
      coffee: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
        tasks: ['coffee:dist']
      },
      coffeeTest: {
        files: ['test/spec/{,*/}*.coffee'],
        tasks: ['coffee:test']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['copy:styles', 'autoprefixer']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      },
      includeSource: {
        files: ['<%= yeoman.app %>/index.html'],
        tasks: ['includeSource:server']
      }
    },
    autoprefixer: {
      options: ['last 1 version'],
      dist: {
        files: [
          {
            expand: true,
            cwd: '.tmp/styles/',
            src: '{,*/}*.css',
            dest: '.tmp/styles/'
          }
        ]
      }
    },
    connect: {
      options: {
        port: 8080,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        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;
          return [require('connect-modrewrite')(['!(\\..+)$ / [L]'])].concat(
            optBase.map(function (path) {
              return connect.static(path);
            }));
        }
      },
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>'
          ]
        }
      },
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= yeoman.app %>'
          ]
        }
      },
      dist: {
        options: {
          base: '<%= yeoman.dist %>'
        }
      }
    },
    clean: {
      dist: {
        files: [
          {
            dot: true,
            src: [
              '.tmp',
              '<%= yeoman.dist %>/*',
              '!<%= yeoman.dist %>/.git*'
            ]
          }
        ]
      },
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js'
      ]
    },
    coffee: {
      options: {
        sourceMap: true,
        sourceRoot: ''
      },
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= yeoman.app %>/scripts',
            src: '{,*/}*.coffee',
            dest: '.tmp/scripts',
            ext: '.js'
          }
        ]
      },
      test: {
        files: [
          {
            expand: true,
            cwd: 'test/spec',
            src: '{,*/}*.coffee',
            dest: '.tmp/spec',
            ext: '.js'
          }
        ]
      }
    },
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: '<%= yeoman.app %>/bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false
      },
      dist: {},
      server: {
        options: {
          debugInfo: true
        }
      }
    },
    // not used since Uglify task does concat,
    // but still available if needed
    /*concat: {
     dist: {}
     },*/
    rev: {
      dist: {
        files: {
          src: [
            '<%= yeoman.dist %>/scripts/{,*/}*.js',
            '<%= yeoman.dist %>/styles/{,*/}*.css',
            //'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
            //'<%= yeoman.dist %>/styles/fonts/*'
          ]
        }
      }
    },
    useminPrepare: {
      // changed from app to dist, to take index.html processed by includeSource in dist
      html: '<%= yeoman.dist %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>'
      }
    },
    usemin: {
      //html: ['<%= yeoman.dist %>/{,*/}*.html'],
      html: ['<%= yeoman.dist %>/index.html'],
      //css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      //js: ['<%= yeoman.dist %>/scripts/**/*.js'],
      options: {
        dirs: ['<%= yeoman.dist %>']
      }
    },
    imagemin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= yeoman.app %>/images',
            src: '{,*/}*.{png,jpg,jpeg}',
            dest: '<%= yeoman.dist %>/images'
          }
        ]
      }
    },
    svgmin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= yeoman.app %>/images',
            src: '{,*/}*.svg',
            dest: '<%= yeoman.dist %>/images'
          }
        ]
      }
    },
    cssmin: {
      // By default, your `index.html` <!-- Usemin Block --> will take care of
      // minification. This option is pre-configured if you do not wish to use
      // Usemin blocks.
      // dist: {
      //   files: {
      //     '<%= yeoman.dist %>/styles/main.css': [
      //       '.tmp/styles/{,*/}*.css',
      //       '<%= yeoman.app %>/styles/{,*/}*.css'
      //     ]
      //   }
      // }
    },
    htmlmin: {
      dist: {
        options: {
          /*removeCommentsFromCDATA: true,
           // https://github.com/yeoman/grunt-usemin/issues/44
           //collapseWhitespace: true,
           collapseBooleanAttributes: true,
           removeAttributeQuotes: true,
           removeRedundantAttributes: true,
           useShortDoctype: true,
           removeEmptyAttributes: true,
           removeOptionalTags: true*/
        },
        files: [
          {
            expand: true,
            cwd: '<%= yeoman.app %>',
            src: ['*.html', 'views/**/*.html', 'blocs/**/*.html'],
            dest: '<%= yeoman.dist %>'
          }
        ]
      }
    },
    // Put files not handled in other tasks here
    copy: {
      dist: {
        files: [
          {
            expand: true,
            dot: true,
            cwd: '<%= yeoman.app %>',
            dest: '<%= yeoman.dist %>',
            src: [
              '*.{ico,png,txt}',
              '.htaccess',
              'bower_components/**/*',
              'images/{,*/}*.{gif,webp}',
              'assets/**/*',
              'fonts/**/*',
              'styles/fonts/*'
            ]
          },
          {
            expand: true,
            cwd: '.tmp/images',
            dest: '<%= yeoman.dist %>/images',
            src: [
              'generated/*'
            ]
          }
        ]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },
    concurrent: {
      server: [
        'coffee:dist',
        'compass:server',
        'copy:styles'
      ],
      test: [
        'coffee',
        'compass',
        'copy:styles'
      ],
      dist: [
        'coffee',
        'compass:dist',
        'copy:styles',
        'imagemin',
        'svgmin',
        'htmlmin'
      ]
    },
    //karma: {
    //  unit: {
    //    configFile: 'karma.conf.js',
    //    singleRun: true
    //  }
    //},
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },
    ngmin: {
      dist: {
        files: [
          {
            expand: true,
            cwd: '<%= yeoman.dist %>/scripts',
            src: '*.js',
            dest: '<%= yeoman.dist %>/scripts'
          }
        ]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '<%= yeoman.dist %>/scripts/scripts.js'
          ]
        }
      }
    },

    preprocess: {
      options: {
        context: {
          // /!\ Security warning:
          // On heroku, process.env might contain sensitive information. (such as DATABASE_URL)
          // To make sure what fields we pass, we specify every field explicitly.
          // So DON'T do this: ENV: JSON.stringify(process.env)

          ENV: JSON.stringify({
            FIRST_VAR: process.env.FIRST_VAR || '',
            SECOND_VAR: process.env.SECOND_VAR || '',
            NODE_ENV: process.env.NODE_ENV || 'production'
          })
        }
      },
      server: {
        src: 'app/scripts/config.js', dest: 'app/scripts/config.processed.js'//dest: '.tmp/scripts/config.js'
      },
      dist: {
        src: 'app/scripts/config.js', dest: 'app/scripts/config.processed.js'//dest: 'dist/scripts/config.js'
      }
    },
    includeSource: {
      options: {
        basePath: 'app',
        baseUrl: '/',
      },
      server: {
        files: {
          '.tmp/index.html': 'app/index.html'
        }
      },
      dist: {
        files: {
          'dist/index.html': 'app/index.html'
        }
      }
    }
  });

  grunt.registerTask('server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'includeSource:server',
      'preprocess:server',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test'
    //'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'includeSource:dist',
    'preprocess:dist',
    'useminPrepare',

    'concurrent:dist',
    'autoprefixer',
    'concat',
    'copy:dist',
    'cdnify',
    'ngmin',
    'cssmin',
    //'uglify',
    'rev',
    'usemin'
  ]);

  grunt.registerTask('default', [
    'jshint',
    'test',
    'build'
  ]);

  // building the app on heroku
  grunt.registerTask('heroku', 'build');

};
2
Can you post the Grunt config?imcg
@imcg added my GruntfileBenjamin Crouzier
Sorry I can't see where you're concatenating files in that Gruntfile. Can't see preprocess either..imcg

2 Answers

3
votes

I had the same problem, and I resolved with the following steps:

1) Use a Build Type different that "js" for your script, so the usemin plugin will ignore it on the build process. For example, "dev".

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

Note that there is no spaces due to a bug on usemin (Issue 128). I'm running usemin version 2.1.1

With this, grunt sever will use your config.js from your Source Code.

2) Modify your preprocess task to put your preprocessed file in a temporary folder, like .tmp. For example:

dist: {
        src: 'app/scripts/config.js', dest: '.tmp/scripts/config.processed.js'
      }

3) Now you need to create a Grunt Task to Concat your config.processed.js in .tmp folder with the scripts/application.js in your dist folder that was generated dynamically by usemin.

For example:

concat: {
          environment: {
              dest: 'dist/scripts/application.js',
              src: ['dist/scripts/application.js', '.tmp/scripts/config.processed.js']
          }
        },

If you are on Windows, you will need to use \\ instead of /

4) Finally, you need to put all together. Your build task should look like this:

 grunt.registerTask('build', [
    'clean:dist',
    'includeSource:dist',
    'preprocess:dist',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'copy:dist',
    'cdnify',
    'ngmin',
    'cssmin',
    'concat:environment',
    'uglify',
    'rev',
    'usemin'
  ]);
0
votes

With this package:

https://www.npmjs.org/package/grunt-file-append

You could append the generated file to the concatenated temp file.

You should remove the config js's reference completely from the html, and add the file-append task after the concat task in the build tasklist to append the generated config file to the concatenated js.