Skip to content

Instantly share code, notes, and snippets.

@asizer
Last active February 11, 2024 23:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save asizer/963a5f268679d790f386 to your computer and use it in GitHub Desktop.
Save asizer/963a5f268679d790f386 to your computer and use it in GitHub Desktop.
Grunt tasks for concatenating and minifying AMD modules and templates

#Use Grunt to concat and minify AMD modules and templates

##About This is an example Gruntfile to concatenate and minify AMD modules and templates. This can be used both for custom code and for existing AMD modules. For instance, the concat:esri task above can be used to concatenate a series of esri AMD modules that aren't included in init.js and would otherwise be loaded separately.

##To use The Gruntfile should be changed to reflect individual projects' file strucutres. This particular implementation expects a file structure of:

package.json
Gruntfile.js
index.html
js/
  main.js // application entry point, with locationPath and packages
  app/
  widgets/
lib/

Make sure Grunt is installed globally.

In the root directory of the project, run npm install then grunt dev to get a concatenated, unminified version of all the AMD modules and templates.

To concat and minify the modules, run grunt build.

To pull down the entire esri api, run grunt esri. After pulling down the api, this will concatenate all the modules listed under the concat:esri task. grunt esri only has to be run once. When the module list is changed, grunt dev will re-concatenate the esri modules as well as the app modules.

##Background and structure of concatenated file Looking at a formatted version of http://js.arcgis.com/3.12/init.js, one can see the structure of a concatenated series of AMD modules is:

require({
  cache: {
    'path/to/js/module': function() {
      module code
    },
    'path/to/js/module2': function() {
      module2 code
    }
    ...
  }
});

Additionally, html templates could be included in this cache (seen at the bottom of init.js):

require({
  cache: {
    // lots of js modules...
    ...
    // html templates:
    'url:path/to/html/template': '<div class=...',
    'url:path/to/html/template2': '<div><div id=...'
  }
});

Thus, the Gruntfile above uses the concat task, its banner, footer, and separator options, and its individual file processing to recreate this structure.

##TODO Currently a script tag for js/main.js, the entry point into the application, is required in index.html. Additionally, main.js doesn't use the cached modules in js/compiled.js -- it loads the modules individually. In this particular application, main.js only needs one module, so it's not that big a deal. However, it seems like there should be a way to automatically trigger main.js to run. I've tried putting the contents of main.js in a script tag in index.html, and I've tried including main.js in compiled.js and then requiring main from index.html. Neither of these have worked, but I'm still thinking about it...

/*global module:false*/
module.exports = function(grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
// Project configuration.
grunt.initConfig({
// use esri_slurp to download api locally. This allows a custom build of the
// esri modules used that aren't in init.js. One could also do this with
// dojo, dijit, and dojox modules.
esri_slurp: {
options: {
version: '3.12'
},
dev: {
options: {
// NOTE: issue w/ beautify on Win:
// https://github.com/steveoh/grunt-esri-slurp/issues/31
beautify: false
},
dest: 'lib/esri'
}
},
concat: {
options: {
banner: 'require({\ncache: {\n',
footer: '\n}});',
separator: ',' + grunt.util.linefeed
},
dev: {
files: {
'js/compiled.js': [
// here are all the files that are going to be crammed into compiled.js
// vary the file structure as appropriate, but also be mindful of changes
// that may need to be made in the process function below.
'js/app/**/*.js',
'js/widgets/**/*.js',
'js/**/*.html'
]
},
options: {
process: function(src, filepath) {
// put a comment at the top of each module with the actual src path
var returnStr = '// Source: ' + filepath + '\n';
if (filepath.indexOf('.js', filepath.length - 3) >= 0) {
// javascript file. add file name, stripping out the leading js/ folder name and the .js file extension.
returnStr += '\'' + filepath.replace('js/', '').replace('.js', '') + '\': ';
// add function and file text
returnStr += 'function() {\n' + src + '}';
} else if (filepath.indexOf('.html', filepath.length - 5) >= 0) {
// html file. add file name as url, stripping out leading js/ folder
returnStr += '\'url:' + filepath.replace('js/', '') + '\': ';
// add html template, stripping out newlines and tabs
returnStr += '\'' + src.replace(/\n\s*/g, '') + '\'';
} else {
console.log('uh oh! ' + filepath + ' fell through');
return;
}
return returnStr;
}
}
},
esri: {
files: {
'lib/esri-compiled.js': [
// this is a list all the esri modules that aren't in init.js that your app is using.
// Yes, it requires a few iterations of watching the network tab for these modules,
// and listing them here manually.
'lib/esri/dijit/Legend.js',
'lib/esri/layers/ArcGISImageServiceLayer.js',
'lib/esri/dijit/BasemapGallery.js',
'lib/esri/dijit/PopupMobile.js',
'lib/esri/dijit/OverviewMap.js',
'lib/esri/virtualearth/VETiledLayer.js',
'lib/esri/dijit/InfoView.js',
'lib/esri/layers/WebTiledLayer.js',
'lib/esri/dijit/NavigationBar.js',
'lib/esri/layers/ImageServiceParameters.js',
'lib/esri/dijit/_TouchBase.js',
'lib/esri/dijit/Basemap.js',
'lib/esri/dijit/BasemapLayer.js',
'lib/esri/layers/MosaicRule.js',
'lib/esri/tasks/ImageServiceIdentifyTask.js',
'lib/esri/tasks/ImageServiceIdentifyResult.js',
'lib/esri/tasks/ImageServiceIdentifyParameters.js',
'lib/esri/dijit/templates/OverviewMap.html',
'lib/esri/dijit/templates/BasemapGallery.html'
]
},
options: {
process: function(src, filepath) {
// put a comment at the top of each module with the actual src path
var returnStr = '// Source: ' + filepath + '\n';
if (filepath.indexOf('.js', filepath.length - 3) >= 0) {
// javascript file. add file name, stripping out the leading js/ folder name and the .js file extension.
returnStr += '\'' + filepath.replace('lib/', '').replace('.js', '') + '\': ';
// add function and file text
returnStr += 'function() {\n' + src + '}';
} else if (filepath.indexOf('.html', filepath.length - 5) >= 0) {
// html file. add file name as url, stripping out leading js/ folder
returnStr += '\'url:' + filepath.replace('lib/', '') + '\': ';
// add html template, stripping out newlines and tabs
returnStr += '\'' + src.replace(/\n\s*/g, '') + '\'';
} else {
console.log('uh oh! ' + filepath + ' fell through');
return;
}
return returnStr;
}
}
}
},
uglify: {
dist: {
options: {
compress: {
drop_console: true
}
},
files: {
'js/compiled.min.js': ['js/compiled.js']
}
}
},
clean: {
esri: ['lib/esri']
}
});
// Default task
grunt.registerTask('default', ['concat:dev']);
// Other tasks
grunt.registerTask('dev', ['concat:dev', 'concat:esri']);
grunt.registerTask('build', ['concat:dev', 'uglify']);
grunt.registerTask('esri', ['clean:esri', 'esri_slurp:dev', 'concat:esri']);
grunt.loadNpmTasks('grunt-contrib-uglify');
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no,width=device-width">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<link rel="shortcut icon" href="favicon.ico">
<title>Grunt concat demo</title>
<!-- Required Esri/Dojo Stylesheets -->
<link rel="stylesheet" href="http://js.arcgis.com/3.12/esri/css/esri.css">
<!-- App Style Sheets -->
<link rel="stylesheet" href="assets/css/main.css">
<link rel="stylesheet" href="assets/css/fontawesome-custom.css">
<!-- Required Libraries -->
<script src="lib/underscore-min.js"></script>
</head>
<body class=''>
<script>
var dojoConfig = {
parseOnLoad: true,
isDebug: true,
async: true // otherwise devtools makes all files VMs. :\
};
</script>
<!-- Esri JSAPI -->
<script src="http://js.arcgis.com/3.12/"></script>
<script src="js/compiled.js"></script>
<script src="lib/esri-compiled.js"></script>
<!-- Application Entry Point -->
<script src="js/main.js"></script>
<div id="main-container"></div>
</body>
</html>
{
"name": "my-app",
"version": "0.0.1",
"description": "",
"main": "Gruntfile.js",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-esri-slurp": "^1.3.2",
"load-grunt-tasks": "^0.6.0",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-uglify": "^0.6.0",
"grunt-contrib-concat": "^0.5.0"
},
"author": "ESRI PS",
"license": "BSD-2-Clause"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment