automatically change file reference in index.html after minification in grunt - gruntjs

i am using grunt file in my project. in this file i want to add the process in which it will be able to replace the previous reference of minified file with the new one in index.html.
i am minifying the files with name and date/time e.g mm_10/11/2016_5:00.min.js after its minification the time will be changed mm_10/11/2016_6:30.min.js. but the index page is having the previous file i.e mm_10/11/2016_5:00.min.js. so i want to replace the mm_10/11/2016_5:00.min.js with mm_10/11/2016_6:30.min.js.
kindly help me here
thanks

To make this process dynamic you could consider configuring your grunt Grunfile.js to also generate the minified JavaScript file using grunt-contrib-uglify
So firstly add that plugin to your package via the CLI if you don't already have it installed:
$ npm install grunt-contrib-uglify --save-dev
As the Gruntfile.js is a JavaScript file you can dynamically retrieve the current date/time properties for naming the new minified JavaScript file using the new Date() construct. This can be obtained using an IIFE (Immediately-invoked Function Expression) which assigns the resultant filename value to an object key, (E.g. newJSFileName), inside the function call grunt.initConfig().
You can then use a Regular Expression in the grunt-text-replace task to find the the previous minified filename: E.g.
/src=\"js\/mm_[0-9]{1,2}-[0-9]{1,2}-[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2}-[0-9]{1,2}.min.js\"/g
The Regex above will match any instances of a string as follows.
src="js/mm_10-12-2016-09-38-59.min.js"
from the <script> tag within your index.html file. E.g.
<script type="text/javascript" src="js/mm_10-12-2016-09-38-59.min.js"></script>
NOTE: The filename show here differs slightly from the naming convention show in my previous post, this time it also includes the seconds prior to the .min.js part. This will avoid issues if the $ grunt command is executed within less than a minute duration from when it was last run.
This seems to work well if you configue your Guntfile.js as follows:
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
/* CREATE THE FILENAME STRING */
newJsFileName: (function() {
// Retrieve the current date and assign parts to variables
var date = new Date(),
day = date.getDate(),
month = date.getMonth() + 1,
year = date.getFullYear(),
hour = date.getHours(),
mins = date.getMinutes(),
secs = date.getSeconds();
// Prefix the day value with a zero when less than 10
if (month < 10) {
month = '0' + month;
}
// Prefix the day value with a zero when less than 10
if (day < 10) {
day = '0' + day;
}
// Prefix the hour value with a zero when less than 10
if (hour < 10) {
hour = '0' + hour;
}
// Prefix the minutes value with a zero when less than 10
if (mins < 10) {
mins = '0' + mins;
}
// Prefix the seconds value with a zero when less than 10
if (secs < 10) {
secs = '0' + secs;
}
// Concatenate the date properties to form the new
// filename. E.g. mm_10-12-2016-12-52-05.min.js
return 'mm_' + month + '-' + day + '-' + year + '-' + hour + '-' + mins + '-' + secs + '.min.js';
}()),
/* COPY THE HTML FILE */
copy: {
main: {
files: [{
expand: false,
src: 'src/index.html',
dest: 'dist/index.html'
}]
}
},
/* MINIFY THE JAVASCRIPT */
uglify: {
my_target: {
files: {
// NOTE: The source file here that we're looking for is already minified.
// E.g. 'src/js/*.min.js'
// however, you will probably want to replace this accordingly as minifying
// a file that is already minified seems unecessary.
'dist/js/<%= newJsFileName %>': 'src/js/*.min.js' // Destination : Source
}
}
},
/* REPLACE LINK TO CSS IN HTML FILE */
replace: {
javaScriptLink: {
src: ['./dist/index.html'],
overwrite: true,
replacements: [{
// UTILIZE A REGULAR EXPRESSION TO FIND THE PREVIOUS MINIFIED FILE NAME STRING
// FROM WITHIN THE .HTML FILE
from: /src=\"js\/mm_[0-9]{1,2}-[0-9]{1,2}-[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2}-[0-9]{1,2}.min.js\"/g,
// THE NAME OF THE NEW MINIFIED FILE
to: 'src="js/<%= newJsFileName %>"'
}]
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-text-replace');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('default', [
'copy',
'uglify',
'replace:javaScriptLink'
]);
};
Before running the $ grunt command with the configuration shown above you will need to make sure that your index.html has the src attribute of the <script> tag set to use the naming convention that includes the additional seconds attribute, otherwise the Regex defined in the Gruntfile.js will not work. E.g.
<script type="text/javascript" src="js/mm_10-12-2016-09-38-59.min.js"></script>
Also, ensure your JavaScript file is named accordingly.
Hope this helps!

Related

How to use grunt to copy a series of files to the same directory they come from?

I have a series of directories like...
component A
- t9n
- componentAT9n.json
component B
- t9n
- componentBT9N.json
Where each of these directories I need to duplicate the one file ending in t9n.json to _en.json ultimately ending up with...
component A
- t9n
- componentAT9n.json
- componentAT9n_en.json
component B
- t9n
- componentBT9N.json
- componentBT9n_en.json
Using grunt is how I'm trying to do it, but I'm having a hard time figuring out how to copy each file it matches to the same directory.
Is this something I can accomplish just using grunt and grunt-contrib-copy ? Or is there maybe another plugin to do this?
In the copy task, I know I can use glob patterns to dynamically grab the source, but the destination I'm unsure of.
files: [
{
cwd: "src/app/js",
src: ["**/t9n/*.json"],
dest: ???,
expand: true
}
]
This situation ended up being just too complicated that I had to make a custom grunt task for it, this is what I did.
// Copies the root t9n file for each t9n directory to an _en.json file
grunt.registerTask("create-en-t9n-files", function() {
grunt.file
.expand("path/to/files/**/t9n/t9n+([A-Za-z0-9]).json")
.forEach(function(source) {
var destinationPath = source.split("/t9n/")[0] + "/t9n/";
var fileName = source.split("/t9n/")[1].split(".json")[0];
var enName = fileName + "_en.json";
grunt.file.copy(source, destinationPath + enName);
});
});

Grunt-string-replace using an object of key-value pairs

I started using grunt in my build process. In package.json I include variables to be replaced this way:
{
"name": "myApp",
"variableToReplace":{
"var1":"var1_replacement",
"var2":"var2_replacement"
},
...
}
However, I do not know how to use this variableToReplace in Gruntfile.js so that it will automatically look up all properties then replace them with the corresponding values. Is it possible using Grunt?
'string-replace': {
dist: {
files: {
//A list of files
},
options: {
//What to put here?
}
}
}
Revised Answer (after comment)
... Is there anyway to loop through the key-value pair of variableToReplace and replace, for example, var1 with var1_replacement.
Yes, this can be achieved by utilizing a Custom Task. The custom task should perform the following:
Read the variableToReplace object from package.json.
Dynamically build the options.replacements array.
Configure/set the option.replacements array using grunt.config.set
Then finally run the task using grunt.task.run.
The following Gruntfile.js demonstrates this solution:
Gruntfile.js
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-string-replace');
grunt.initConfig({
'string-replace': {
dist: {
files: [{
expand: true,
cwd: 'src/',
src: '**/*.css',
dest: 'dist/'
}],
options: {
replacements: [] // <-- Intentionally empty and will be dynamically
// configured via `configAndRunStringReplace`.
}
}
}
});
/**
* Helper task to dynamically configure the Array of Objects for the
* `options.replacements` property in the `dist` target of the `string-replace`
* task. Each property name of the `variableToReplace` Object (found in
* `package.json`) is set as the search string, and it's respective value
* is set as the replacement value.
*/
grunt.registerTask('configAndRunStringReplace', function () {
// 1. Read the `variableToReplace` object from `package.json`.
var replacements = grunt.file.readJSON('package.json').variableToReplace,
config = [];
// 2. Dynamically build the `options.replacements` array.
for (key in replacements) {
config.push({
pattern: new RegExp(key, 'g'),
replacement: replacements[key]
});
}
// 3. Configure the option.replacements values.
grunt.config.set('string-replace.dist.options.replacements', config);
// 4. Run the task.
grunt.task.run('string-replace:dist');
});
// Note: In the `default` Task we add the `configAndRunStringReplace`
// task to the taskList array instead of `string-replace`.
grunt.registerTask('default', ['configAndRunStringReplace']);
}
Important note regarding Regular Expressions:
The docs for grunt-string-replace states:
If the pattern is a string, only the first occurrence will be replaced...
To ensure that multiple instances of the search/find string are matched and replaced the configAndRunStringReplace custom task utilizes a Regular Expression with the global g flag.
So, any instances of the following regex special characters:
\ ^ $ * + ? . ( ) | { } [ ]
which may be used in a search word (i.e. as a key/property name in your package.json) will need to be escaped. The typical way to escape these characters in a Regex is to add a backslash \ before the character (E.g. \? or \+ etc..). However, because you're using key/property names in JSON to define your search word. You'll need to double escape any of the previously mentioned characters to ensure your JSON remains valid. For Example:
Lets say you wanted to replace question marks (?) with exclamation marks (!). Instead of defining those rules in package.json like this:
...
"variableToReplace":{
"?": "!"
},
...
Or like this:
...
"variableToReplace":{
"\?": "!"
},
...
You'll need to do this (i.e. use double escapes \\):
...
"variableToReplace":{
"\\?": "!"
},
...
Original Answer
The following contrived example shows how this can be achieved:
Gruntfile.js
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-string-replace');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), // 1. Read the package.json file.
'string-replace': {
dist: {
files: {
'dist/': ['src/css/**/*.css'],
},
options: {
replacements: [{
// 2. Use grunt templates to reference the properties in package.json
pattern: '<%= pkg.variableToReplace.var1 %>',
replacement: '<%= pkg.variableToReplace.var2 %>',
}]
}
}
}
});
grunt.registerTask('default', ['string-replace']);
}
Notes
Add pkg: grunt.file.readJSON('package.json') to the grunt.initConfig() section of your Gruntfile.js. This will parse the JSON data stored in package.json.
Use grunt templates to access the properties of package.json. The standard JavaScript dot notation is used to access the property values, (e.g. pkg.variableToReplace.var1), and is wrapped in a leading <%= and trailing %>
Using the contrived Gruntfile.js configuration above with your the package.json data (as described in your question). The following would occur:
Any instances of the string var1_replacement found in any of the .css files stored in the src/css/ directory will be replaced with the string var2_replacement.
The resultant files will be saved to the dist/ directory.

Grunt Concat many files in many folders while maintaining folder structure

I am using Grunt concat in my project. I want to combine files on a folder level.
E.G.
js >
parent >
child1 >
a.js
b.js
c.js
child2 >
a.js
b.js
into:
js >
parent >
child1 >
child1combined.js
child2 >
child2combined.js
is there a way of doing this without specifically adding each "child" in its own concat line?
I ended up doing the following I found in this post.
grunt.registerTask("taskName", "Task Description", function() {
// get all module directories
grunt.file.expand("src/js/parent/*").forEach(function (dir) {
// get the module name from the directory name
var dirName = dir.substr(dir.lastIndexOf('/')+1);
// get the current concat object from initConfig
var concat = grunt.config.get('concat') || {};
// create a subtask for each module, find all src files
// and combine into a single js file per module
concat[dirName] = {
src: [dir + '/**/*.js'],
dest: 'build/js/parent/' + dirName + '/combined.js'
};
// add module subtasks to the concat task in initConfig
grunt.config.set('concat', concat);
});
});
Then just call taskName from your registerTask.
Use globbing patterns in your concat task. To quote the linked documentation:
It is often impractical to specify all source filepaths individually,
so Grunt supports filename expansion (also know as globbing).
I know this topic is old, but I want to share my solution because I couldn't find a simple solution working as expected on the web.
concat: {
files: [
{
expand: true,
src: ["**/*.js"],
dest: "dest/js",
cwd: "src/js",
rename: function(dst, src) {
const srcParts = String(src).split("/");
let dstPath = "";
let dstName = srcParts[0].split(".")[0];
if (srcParts.length > 1) {
dstName = srcParts[srcParts.length - 2];
dstPath =
srcParts
.slice(0, srcParts.length - 1)
.join("/") + "/";
}
return `${dst}/${dstPath + dstName}.js`;
}
}
]
}
I hope this helps someone :)

How to get grunt.file.readJSON() to wait until file is generated by another task

I'm working on setting up series of grunt tasks that work with RequireJS r.js compiler:
1) generates a .json file listing of all files in a directory
2) strips the ".js" from the filename (requirejs requires this)
3) use grunt.file.readJSON() to parse that file and use as a configuration option in my requirejs compilation task.
Here is the relevant code from my gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
// create automatic list of all js code modules for requirejs to build
fileslist: {
modules: {
dest: 'content/js/auto-modules.json',
includes: ['**/*.js', '!app.js', '!libs/*'],
base: 'content/js',
itemTemplate: '\t{' +
'\n\t\t"name": "<%= File %>",' +
'\n\t\t"exclude": ["main"]' +
'\n\t}',
itemSeparator: ',\n',
listTemplate: '[' +
'\n\t<%= items %>\n' +
'\n]'
}
},
// remove .js from filenames in module list
replace: {
nodotjs: {
src: ['content/js/auto-modules.json'],
overwrite: true,
replacements: [
{ from: ".js", to: "" }
]
}
},
// do the requirejs bundling & minification
requirejs: {
compile: {
options: {
appDir: 'content/js',
baseUrl: '.',
mainConfigFile: 'content/js/app.js',
dir: 'content/js-build',
modules: grunt.file.readJSON('content/js/auto-modules.json'),
paths: {
jquery: "empty:",
modernizr: "empty:"
},
generateSourceMaps: true,
optimize: "uglify2",
preserveLicenseComments: false,
//findNestedDependencies: true,
wrapShim: true
}
}
}
});
grunt.loadNpmTasks('grunt-fileslist');
grunt.loadNpmTasks('grunt-text-replace');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.registerTask('default', ['fileslist','replace', 'requirejs']);
I'm running into a problem where, if the "content/js/auto-modules.json" file doesn't already exist on load of my config file, the file.readJSON() is executed immediately, before the file exists and the entire task fails and throws "Error: Unable to read file " If the file already exists, everything works beautifully.
How can I set this up so that the task configuration waits for that file to be created in the first task, and modified in the second task before it tries to load & parse the JSON in it for the third task? Or is there another way (perhaps using a different plugin) to generate a json object in one task, and then pass that object to another task?
Old post but I had a similar experience.
I was trying to load a some json config like:
conf: grunt.file.readJSON('conf.json'),
but if this file did not exist then it would fall in a heap and not do anything.
So I did the following to load it and populate defaults if it didnt exist:
grunt.registerTask('checkConf', 'ensure conf.json is present', function(){
var conf = {};
try{
conf = grunt.file.readJSON('./conf.json');
} catch (e){
conf.foo = "";
conf.bar = "";
grunt.file.write("./conf.json", JSON.stringify(conf) );
}
grunt.config.set('conf', conf);
});
You still may have some timing issues but this approach may help someone with a readJSON error.

Dynamic mapping for destinations in grunt.js

I have a project with several sub folders that contain JavaScript files I want to concatenate. what would be the right way to configure them?
eg.
source: /modules/$modulename/js/*.js (several files)
dest: /modules/$modulename/js/compiled.js
So what I want to do is to compile js-files of an unknown/unconfigured count of subfolders ($modulename) into one file per subfolder.
Is this possible?
The following function (built after hereandnow78's instructions) does the job:
grunt.registerTask('preparemodulejs', 'iterates over all module directories and compiles modules js files', function() {
// read all subdirectories from your modules folder
grunt.file.expand('./modules/*').forEach(function(dir){
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: [dir + '/js/*.js', '!' + dir + '/js/compiled.js'],
dest: dir + '/js/compiled.js'
};
// save the new concat config
grunt.config.set('concat', concat);
});
});
after that i put preparemodulejs before the concat job in my default configuration.
you will probably need to code your own task, where you iterate over your subfolders, and dynamically append to your concat configuration.
grunt.registerTask("your-task-name", "your description", function() {
// read all subdirectories from your modules folder
grunt.file.expand("./modules/*").forEach(function (dir) {
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: ['/modules/' + dir + '/js/*.js', '!/modules/' + dir + '/js/compiled.js'],
dest: '/modules/' + dir + '/js/compiled.js'
};
// save the new concat configuration
grunt.config.set('concat', concat);
});
// when finished run the concatinations
grunt.task.run('concat');
});
run this with:
$ grunt your-task-name
this code is untested, but i think it should do your job.
HINT: you can put this code into an external file and include in your gruntfile if you want to keep your gruntfile small, e.g. put this into a file inside a tasks-directory:
module.exports = function(grunt) {
grunt.registerTask("your-task-name", "your description", function() {
...
});
};
and load in in your gruntfile:
grunt.loadTasks("./tasks");

Resources