Adrian Grigore
Adrian Grigore

Reputation: 33318

How can I easily reference separate js / css files for debugging when using gulp or grunt?

I'm developing a legacy ASP.NET MVC 5 project which still uses ASP.NET Bundling and Minification. I'm interested in switching to Gulp or Grunt, because I need to save source maps for my js files.

It seems easy to generate a minified script bundle with Gulp or Grunt, but what I do not understand yet is the recommended setup for loading single js files when debugging and minified bundles in production. I guess it would be quite easy to generate a razor view for including the scripts as part of my Grunt / Gulp compilation process, but it feels like re-inventing the wheel.

For instance, in ASP.NET MVC i can write something like this:

@Scripts.Render("~/bundles/MyJSBundle")

and it will automatically load separate js files in development and a single script bundle in production. What is the easiest way achieve this with Gulp or Grunt?

Upvotes: 0

Views: 328

Answers (1)

RobC
RobC

Reputation: 25002

Short answer:

Typically when using Grunt you generate two builds - one for "dev" (development) and another for "dist" (distribution/production). Whereby for the scenario you've described;

  • Both the "dev" and "dist" builds generate a single concatenated/minified file version (e.g. bundle.min.js) derived from multiple source .js files.
  • However, only the "dev" build generates an additional Source Map file(s), that holds information about your original .js files, for the purpose of debugging during the development lifecycle.

Grunt plugins, such as grunt-processhtml, provide a way to update any links to .js assets in the .html file. For example, let's say your source .html contains these two links;

<script src="js/a.js"/>
<script src="js/b.js"/>

They can be substituted during the "dist" and/or "dev" build step to the following single <script> element:

<script src="dir/bundle.min.js"/> 

Example demo:

The following somewhat contrived example demonstrates how you may approach your requirement using Grunt.

  1. Let's say our initial project directory is structured as follows:

    project
    ├── Gruntfile.js
    ├── node_modules
    │   └── ...
    ├── package.json
    └── src
        ├── index.html
        └── js
            ├── a.js
            └── b.js
    

    Note, in the src directory we have a single index.html file, and two .js files in the js directory.

  2. In the contents of index.html shown below it contains two <script> elements, each one referencing a .js file.

    project/src/index.html

    <!DOCTYPE html>
    
    <html>
    
    <head>
      <meta charset="utf-8" />
      <title>demo</title>
    </head>
    
    <body>
    
      <!--build:js js/bundle.min.js-->
      <script src="js/a.js"></script>
      <script src="js/b.js"></script>
      <!--/build-->
    </body>
    
    </html>
    

    Note, the custom HTML comments encasing both <script> elements. These custom HTML comments are utilized by grunt-processhtml. The part that reads; js/bundle.min.js in the comment essentially defines the new pathname to be used.

  3. Let's consider the following Gruntfile.js configuration:

    Gruntfile.js

    module.exports = function (grunt) {
    
      grunt.loadNpmTasks('grunt-contrib-concat');
      grunt.loadNpmTasks('grunt-contrib-uglify');
      grunt.loadNpmTasks('grunt-processhtml');
    
      grunt.initConfig({
    
        // 1. Concatenate .js files.
        concat: {
          dist: {
            src: [
              'src/js/a.js',
              'src/js/b.js'
            ],
            dest: './dist/js/bundle.min.js'
          },
          dev: {
            options: {
              sourceMap: true
            },
            src: [
              'src/js/a.js',
              'src/js/b.js'
            ],
            dest: './dev/js/bundle.min.js'
          }
        },
    
        // 2. Minify .js files.
        uglify: {
          dist: {
            files: {
              './dist/js/bundle.min.js': './dist/js/bundle.min.js' // dest : src
            }
          },
          dev: {
            options: {
              mangle: false,
              sourceMap: true,
              sourceMapIn: './dev/js/bundle.min.js.map'
            },
            files: {
              './dev/js/bundle.min.js': './dev/js/bundle.min.js' // dest : src
            }
          }
        },
    
        // 2. Process .html file.
        processhtml: {
          dist: {
            files: {
              './dist/index.html': './src/index.html' // dest : src
            }
          },
          dev: {
            files: {
              './dev/index.html': './src/index.html' // dest : src
            }
          }
        }
    
      });
    
    
      grunt.registerTask('default', ['dist', 'dev']);
    
      grunt.registerTask('dist', [
        'concat:dist',
        'uglify:dist',
        'processhtml:dist'
      ]);
    
      grunt.registerTask('dev', [
        'concat:dev',
        'uglify:dev',
        'processhtml:dev'
      ]);
    
    };
    

    Explanation of Gruntfile.js:

    • In addition to the previously mentioned grunt-processhtml plugin the following two are also utilized in this example:

      • grunt-contrib-concat - for concatenating the two .js files.

      • grunt-contrib-uglify - for minifying the .js file.

        Note: There are other plugins available for these types of task. I have chosen these additional two plugins for the purpose of this demonstration.

    • Each of the three Tasks (concat, uglify, and processhtml) contain two separate Targets named dist and dev. The main differences in each Target are:

      • Different dest (destination) paths for the resultant generated .jsfile(s).
      • For the concat:dev and uglify:dev Targets its options object defines the configuration for the resultant Source Map file.
    • At the end of Gruntfile.js three different grunt.registerTask() have been defined. Each one defines a taskList that essentially defines which Task and Target to run in the order specified.

      For example consider the following registered task named dist:

      grunt.registerTask('dist', [
        'concat:dist',
        'uglify:dist',
        'processhtml:dist'
      ]);
      

      When running grunt dist via the command line Grunt essentially invokes this Task, which subsequently performs the following in this order:

      1. Firstly, runs the dist Target defined in the concat Task.
      2. Then runs the dist Target defined in the uglify Task.
      3. Finally, runs the dist Target defined in the processhtml Task.
  4. Running Gruntfile.js (above) and its output

    1. Running the following command via the command line:

      grunt dev
      

      outputs the following additional assets to the project directory:

      project
      ├── ...
      ├── dev
      │   ├── index.html
      │   └── js
      │       ├── bundle.min.js
      │       └── bundle.min.js.map
      └── ...
      

      As you can see it has:

      • Created a new dev folder in the root of the project directory.

      • The two <script> elements originally defined in project/src/index.html have been substituted in the newly generated project/dev/index.html with a single <script> tag as follows:

        <script src="js/bundle.min.js"></script>
        
      • Both files; project/src/js/a.js and project/src/js/b.js, have been concatenated and minified in the resultant project/dev/js/bundle.min.js.

      • The following source map file has been generated; project/dev/js/bundle.min.js.map. This file essentially maps back to the original project/src/js/a.js and project/src/js/b.js files.

    2. Running the following command via the command line:

      grunt dist
      

      outputs the following additional assets to the project directory:

      project
      ├── ...
      ├── dist
      │   ├── index.html
      │   └── js
      │       └── bundle.min.js
      └── ...
      

      As you can see this time it has;

      • Created a dist folder in the root of the project directory.

      • Again, the two <script> elements originally defined in project/src/index.html have been substituted in the newly generated project/dist/index.html with a single <script> tag (as per the aforementioned dev Task).

      • Again, both files; project/src/js/a.js and project/src/js/b.js, have been concatenated and minified in the resultant project/dist/js/bundle.min.js.

      • However, the main notable difference is that NO source map file has been created.

    3. Running the following command via the command line:

      grunt
      

      will produce both the outputs defined in the previous steps 1 and 2.

Upvotes: 3

Related Questions