concat/uglify js files using grunt - gruntjs

I have the following folder structure
root
src
ui
ui1
js
ui.js
page1
page.js
page2
page.js
ui2
js
ui.js
page1
page.js
page2
page.js
page3
page.js
css
js
ui
Is it possible to run grunt uglify on this to achieve the following: concatenate all js files from each ui[x] folder from root/src/ into the root/ui/ folder keeping the path structure intact?
I would like to end up with
root
src
ui
ui1
js
ui.js
page1
page.js
page2
page.js
ui2
js
ui.js
page1
page.js
page2
page.js
page3
page.js
css
js
ui
ui1
ui.min.js
ui2
ui.min.js
My problem is that ui[x] and page[x] folders will be added later so I would need to make this dynamic.
What I was able to achieve is either concatenate all files without keeping the path or keep the path, uglify but not concatenate. Any help is appreciated.
files: {
expand: true,
cwd: 'src/ui/',
src: '**/*',
dest: 'ui/'
}

The solution to your problem is to implement a rename function. The purpose of this function is to give you finer control over src/dest mappings when your use-case cannot be satisfied by the default configurations. The rename function will be called for each source file that matches your src pattern and will allow you to define the logic that determines the destination file.
This will work even if we map multiple source files to a single destination file. The docs say:
If multiple matched src paths are renamed to an identical destination (i.e. if two different files get renamed to the same file), each output will be added to an array of sources for it.
Our objective, starting from the root/src/ui/ directory, is to gather every filepath ending in ".js" and, for each, produce a destination filepath that is the first directory name plus "/ui.min.js". For example, ui1/js/ui.js would map to ui1/ui.min.js and ui2/page3/page.js would map to ui2/ui.min.js.
With the logic established, the implementation of our rename function is fairly trivial. Our Gruntfile would look like the following:
module.exports = function (grunt) {
grunt.initConfig({
clean: {
dist: ['root/ui']
},
uglify: {
dist: {
files: [
{
expand: true,
cwd: 'root/src/ui/',
src: ['**/*.js'],
dest: 'root/ui/',
rename: function (dest, src) {
return dest + src.substring(0, src.indexOf('/') + 1) + 'ui.min.js';
}
}
]
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['clean', 'uglify']);
};

Related

How do I generate sourcemaps for Uglified files using Grunt?

I have a Grunt project that uses both Browserify and Uglify. Here are the core bits of it:
browserify: {
myapp: {
options: {
transform: ['babelify'],
browserifyOptions: {
debug: true
},
},
src: 'src/index.js',
dest: 'build/myapp.js'
}
},
uglify: {
options: {
sourceMap: true,
banner: bannerContent
},
target: {
src: 'build/myapp.js',
dest: 'build/myapp.min.js'
}
},
It seems to generate a myapp.min.js.map file but it no longer has the raw sources in the source-map that existed prior to the Browserification.
Here's what the resultant source-map file contains:
{
"version":3,
"sources":[
"myapp.js"
],
"names":[
...
...
...
],
"mappings":".........",
"file":"myapp.min.js"
}
I've tried using the uglifyify transform for Browserify but that does not seem to generate as small files as the Uglify task.
I've also bumped all my dependencies to the latest but I haven't been able to resolve this issue.
grunt-contrib-uglify has a sourceMapIn option that allows you to specify the location of an input source map file from an earlier compilation - which in your scenario is the browserify task.
However, whilst setting browserifyOptions: { debug: true } in your browserify task does generate an inline source map in the resultant .js file (i.e. in build/myapp.js), the crux of the problem is twofold:
We don't have an external source map file that we can configure the sourceMapIn option of the subsequent grunt-contrib-uglify task to utilize.
grunt-browserify doesn't provide a feature to create an external .map file, it only creates them inline (see here)
To address the aforementioned issue consider utilizing grunt-extract-sourcemap to extract the inline source map from build/myapp.js (i.e. from the output file generated by your browserify task) after it has been produced.
Gruntfile.js
The following gist shows how your Gruntfile.js should be configured:
module.exports = function (grunt) {
grunt.initConfig({
browserify: {
myapp: {
options: {
transform: ['babelify'],
browserifyOptions: {
debug: true
},
},
src: 'src/index.js',
dest: 'build/myapp.js'
}
},
extract_sourcemap: {
myapp: {
files: {
'build': ['build/myapp.js']
}
}
},
uglify: {
options: {
sourceMap: true,
sourceMapIn: 'build/myapp.js.map'
},
target: {
src: 'build/myapp.js',
dest: 'build/myapp.min.js'
}
}
});
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-extract-sourcemap');
grunt.loadNpmTasks('grunt-contrib-uglify');
// Note the order of the tasks in your task list is important.
grunt.registerTask('default', ['browserify', 'extract_sourcemap', 'uglify']);
};
Explanation
First the browserify task is invoked which outputs a new file (i.e. build/myapp.js) containing your bundled JavaScript and an "inlined" source map info. If you were to inspect the content of build/myapp.js at this stage it includes something like the following at the end:
//# sourceMappingURL=data:application/json;charset=utf-8;base64, ...
Next the extract_sourcemap task is invoked. This essentially extracts the "inlined" source map info from build/myapp.js and writes it to a new file named myapp.js.map which is saved in your build directory.
The original "inlined" source map info in build/myapp.js is replaced with a link to the newly generated source map file, i.e. myapp.js.map. If you inspect the content of build/myapp.js you'll now notice the following at the end of the file instead:
//# sourceMappingURL=myapp.js.map
Lastly the uglify task is invoked. Notice how its sourceMapIn option is configured to read build/myapp.js.map, i.e the source map file we generated at step 2.
This task creates your desired build/myapp.min.js file containing; your minified JS, and a link to a newly generated source map file build/myapp.min.js.map.
Note The final resultant file (i.e. build/myapp.min.js) now correctly maps back to the original src/index.js file and any file(s) that index.js itself may have import'ed or require()'d

How to set CSS Source Map output destination when grunt-contrib-less is used

I am using grunt-contrib-less for compiling less files.
I have grunt locally installed at root of project folder.
The css files are located at qa1/avinash/html5/phase1/css/ path from root of project folder.
So this is the path i am specifying for cwd (current working directory), src and dest parameters of the grunt-less task. there are no issues in compilation of css and source map.
The only issue i face is that the source map is generated in the same folder of gruntfile. but my generated css is at the dest path i specified. since the css and source map are at different locations i have to manually edit the less path references in source map and bring it to the generated css directory. or use sourceMapURL to specify the source map location ../../../../../style.css.map(backwards). Both ways are not convenient.
So can anyone help me how to specify the source map output destination path like we specify for destination path for generated css something like
sourceMapDest: 'qa1/avinash/html5/phase1/css/'
--
currently used Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
less: {
options: {
sourceMap:true,
sourceMapFilename: "style.css.map",
sourceMapURL: '../../../../../style.css.map'
},
src: {
// no need for files, the config below should work
expand: true,
cwd: "qa1/avinash/html5/phase1/css/",
src: "style.less",
dest: "qa1/avinash/html5/phase1/css/",
ext: ".css"
}
},
watch: {
js: {
files: ['qa1/avinash/html5/phase1/css/'],
tasks: ['default'],
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['less']);
};
The sourceMapFilename option may include a path part as well.
I.e. just change it to:
sourceMapFilename: "qa1/avinash/html5/phase1/css/style.css.map"

Using grunt to concat js for multiple pages

I'm very new to grunt. As I was reading examples I wondered how should I handle next case. Lets assume I have this structure:
/pages/layout.php
/pages/index.php
/pages/contacts.php
Layout contains common js (like jquery/bootstrap) while contacts extends layout.php and adds some new js
So, layout uses
jquery.js
bootstrap.js
contacts.php
jquery.js
bootstrap.js
feedback.js
In articles I read the authors usually would create only one file, like frontend.js then uglify it. But if I have multiple js-plugins combination for each page, am I right that my concat configuration should look like:
module.exports = function(grunt) {
// I haven't tested this but that's the way I think it should look
// Common scripts which will be used on every page
var layout = ['/path/to/jquery.js', '/path/to/bootstrap.js'];
grunt.initConfig({
concat: {
options: {
separator: "\n"
},
layout: {
src: layout.concat(['/path/to/index.js']),
dest: '/path/to/result'
},
contacts: {
src: layout.concat(['/path/to/feedback.js']),
dest: '/path/to/result'
}
}
});
}
The config might get quite big, depending on how much pages (which use js) I have. Or there is another approach to do such thing?

Using Grunt Copy to copy images to new directory

I'm using Grunt 0.4.1 and Grunt-contrib-copy version 0.4.1. I'm trying to copy images from a src directory to a distribution directory, but it's copying the full src directory instead of only the contents of the images directory. In other words, this is creating dist/images/src/images/..., whereas I want the result to just be dist/images/...
Here's my copy task:
copy: {
images: {
files: {
'dist/images/': 'src/images/**/*'
}
}
}
And it's being run as follows:
// Default task.
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('default', ['less', 'concat', 'uglify', 'cssmin', 'copy']);
How do I get it so that only the subdirectories of src/images get copied into dist/images?
These files are already compressed and only need to be copied to the destination during build.
What actually fixed my problem was designating the root directory structure from which I was copying with the "cwd" directive and then the src exactly like above. Just came back to post this and saw the same solution basically that I had come up with. Here is what worked for me:
copy: {
images: {
files: [
{
expand: true,
cwd: 'src/images/',
src: ['**/*.{png,jpg,svg}'],
dest:'dist/images/'
}
]
}
}
The "cwd" property allows you to define the root of the source from which you are copying. This will not be included in the path of the copied files. So in my case, by defined "cwd" as "src/images", I'm then able to define the source as "**/*.{png,jpg,svg}" This finds all the subdirectories in the source images directory and only copies the files with the extensions I've provided. This gives me the result I want: dist/images/... with all the subdirectories as expected.
Without using "cwd", the grunt-contrib-copy literally copies the entire source tree structure into the destination. The "cwd" property allows you to cherry pick subdirectories and files from one file structure and copy them into another.
You'll want to specify the flatten option, which removes the directory tree.
copy: {
images: {
expand: true,
cwd: 'src/images/',
src: ['**/*'],
dest: 'dist/images/',
filter: 'isFile',
flatten: true
}
}

Grunt recursive copy

I'm setting up a Grunt script that needs to copy and reorganise directories of images from A to B. Simple enough.
Directory structure:
components
componentA
js
img
imgfolderA
imgfolderB
css
componentB
js
img
imgfolderA
Each img directory could contain other directories and directories within those directories to help organise the images.
I want to use Grunt to take all those images and put them under one directory (assets/img):
assets
img
dirA
imgfolderA
imgfolderB
dirB
imgfolderA
Any ideas on how could I do this in grunt without specifying each component directory (it needs to be fully automated)?
know it's a bit late but this should do the job, use 'grunt-contrib-copy' like so
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
copy: {
production: {
files: [{
expand: true,
cwd: "componentA/img/imgfolderA/",
src: ["*.*", "**/*.*"],
dest: "./assets/img/dirA/",
},
{
expand: true,
cwd: "componentB/img/imgfolderB/",
src: ["*.*", "**/*.*"],
dest: "./assets/img/dirB/",
},
]
}
}
});
// Production Build Tools
grunt.loadNpmTasks('grunt-contrib-copy');
// Default Production Build task(s).
grunt.registerTask('default', ['copy']);
};
ps magic is in the files objects, there not very well documented, but the documentation is here, after one or two reads it makes sense honest!
grunt-contrib-copy setup: https://github.com/gruntjs/grunt-contrib-copy (the readme at the bottom)
files object setup: http://gruntjs.com/configuring-tasks#globbing-patterns
task setup: http://gruntjs.com/configuring-tasks
This is rather simple using grunt.file.expand.
Just pass matching glob patterns (e.g. **/img/**), and then recurse on the returned matching file values to copy.

Resources