Build multi client project with requirejs and grunt - javascript

I'm working on a project where the main code based should be used by a bunch of different client. So we have a requirejs project and my initial idea is to have simple bootstrap.js file that will require an app.js files that is different for every client.
bootstrap.js
requirejs(['app'],function(app){
//some initial code here
app.start();
}
So the project structure will be look like this:
|_bootstrap.js
|_commonModules
|_someModule.js
|_client1
|_app.js
|_modules
|_module.js
|_client2
|_app.js
|_modules
|_module.js
So my ideas is to compile the app for every client using requirejs' r compiler and set the path to app in every compilation to clientX/app.js by crearting a new build.js for every step like this:
({
paths: {
"app": "client1/app"
}
})
So at the moment I have a grunt build task that is using a bunch of other tasks like uglify, usemin, md5 and so on. Can I create a new task that use this task but changing the requireJs settings for every client? Or is there a better way to achieve my goals?

So after all it wasn't that hard. The cool thing is that you can change the configuration for the actual running task and that you can call previous defined task in a running task.
//this was the old task to build one distribution
grunt.registerTask('build', ['clean:build', 'copy:build', 'useminPrepare', 'usemin', 'requirejs', 'concat', 'uglify', 'mincss', 'md5', 'manifest', 'copy:toClientFolder']);
grunt.registerTask('buildAll', function() {
['client1', 'client2'].forEach(function(client) {
//before every build task run a task to change the config
grunt.task.run('updateConfig:' + client, 'build');
});
});
//we need to change the config in a separate task,
//otherwise, change the config just in the forEach, would result in the same
//config for both task, using the client2 settings
grunt.registerTask('updateConfig', function(client) {
var requireJsName = 'requirejs.compile.options.name';
var clientFolder = 'copy.toClientFolder.files';
grunt.config(requireJsName, 'clients/' + client + '/Bootstrap');
grunt.config(clientFolder, [
{expand: true, cwd: 'dist', src: '**', dest: 'dist_' + client}
]);
});
And a app.js file for a client looks like this:
requirejs.config({
paths: {
'commonModules/someModule': 'clients1/modules/module'
}
});
requirejs(['boootstrap',
'commonModules/someModule1'],
function(boootstrap, someModule1) {
$(function() {
boootstrap();
someModule1();
});
});

Related

How to make couple of minified files from different js files using grunt

I am new to grunt and task runners in JS, so this might seem a simple question but I have been unable to find exact working answer.
I have :
concat: {
options: {
// define a string to put between each file in the concatenated output
separator: '\n\n'
},
dist: {
// the files to concatenate
src: ['scripts/app.js', 'scripts/constant.js'
],
// the location of the resulting JS file
dest: 'scripts/custom.js'
}
},
This task collects all my custom file together. What I want is to do similar thing for all my vendors file. Finally I should end up with two js only custom.js having my concatenated-minified code and vendor.js having concatenated-minfied libraries.
How do I write grunt configuration for this. Do I need to make two different tasks. If I write the above code twice with different input files, it seems to run the last code.
grunt-contrib-concat can be configured to utilize multiple-targets.
For further documentation on this subject refer to multi-tasks and Task Configuration and Targets in the grunt documentation.
Gruntfile.js
For your scenario you need to configure your concat task similar to this (Note: the new custom and vendor targets):
module.exports = function(grunt) {
grunt.initConfig({
concat: {
options: {
separator: '\n\n'
},
custom: {
src: ['scripts/app.js', 'scripts/constant.js'],
dest: 'scripts/output/custom.js'
},
vendor: {
// Modify the src and dest paths as required...
src: ['scripts/vendor/foo.js', 'scripts/vendor/baz.js'],
dest: 'scripts/output/vendor.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('concatenate', [
'concat:custom', // <-- Targets in a task are called using a colon separator.
'concat:vendor'
]);
};
Running concat
Using the example gist provided above you can run the concat task via the CLI by typing the following command:
$ grunt concatenate
Configuring Options
If you require different configuration options for both the custom and vendor targets you will need to move the options object inside their respective targets. As explained here.
Note: Using the example gist provided the options specified will apply to both targets.

Grunt - Queueing a task to run after a previous task completes

I looked at the official Grunt documentation for creating tasks here
http://gruntjs.com/creating-tasks
I have two tasks that I want to do, but the second one cannot run until after the first one completes. That's because the second task takes the output from the first task and uses it to create new output.
To break it down
My project involves Bootstrap, so it has a lot of unused code. My first objective is to remove the unused code with uncss. I would then take the output from this new css file and minify it with cssmin.
Here was the exact example from gruntjs
grunt.registerTask('foo', 'My "foo" task.', function() {
// Enqueue "bar" and "baz" tasks, to run after "foo" finishes, in-order.
grunt.task.run('bar', 'baz');
// Or:
grunt.task.run(['bar', 'baz']);
});
I tried to apply this to my code here
grunt.registerTask('default', 'uncss', function() {
grunt.task.run('cssmin');
});
This means that when grunt is entered, the default is to run the uncss task first, wait for it to complete, then run the cssmin task. However I got this output
Running "default" task
Running "cssmin:css" (cssmin) task
1 file created. 3.38kb -> 2.27kb
Done, without errors
Here is my initConfig
uncss: {
dist: {
files: {
'directory/assets/stylesheets/tidy.css': ['directory/*.html', 'directory/views/*.html']
}
}
},
cssmin: {
css: {
files: {
'directory/assets/stylesheets/styles.min.css': ['directory/assets/stylesheets/styles.css']
}
}
}
In other words, I have two stylesheets in my folder. One contains the custom styles I created, and another contains Bootstrap minified. By running uncss, I will get a new css file named tidy.css.
The cssmin task is supposed to look for this tidy.css file and minify it resulting in a new styles.min.css file.
I can get this to work, but I have to manually run one task and then run another one. How can I automate this to have them run in sequence
first, best practice is to use npm package to load all tasks automatically:
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
here are two grunt tasks:
one: {
wake up...
},
two: {
dress up...
},
and here is how you run one after the other
grunt.registerTask('oneThenOther', [
'one',
'two'
]);
You're close. When registering your alias task pass an array of tasks in the sequence you desire instead of a single task.
grunt.registerTask('default', ['uncss', 'cssmin']);
Alternatively, the sequence can be specified via the CLI:
> grunt uncss cssmin
It turns out part of the problem was I was specifying the wrong file under cssmin.
Changing my cssmin config to
cssmin: {
css: {
files: {
'directory/assets/stylesheets/styles.min.css': ['directory/assets/stylesheets/tidy.css']
}
}
}
and then registering the tasks in an array solved it
grunt.registerTask('default', ['uncss', 'cssmin']);
Note that the tasks must be specified in that order.

Use Grunt newer with custom task

I'm trying to use grunt-newer to watch files from a folder and if any is changed, trigger a custom task.
I have something like this in my Gruntfile.js:
grunt.initConfig({
watch: {
widgets: {
files: "/somepath/*.js",
tasks: ['newer:mycustomtask']
}
}
});
grunt.registerTask("mycustomtask", ["description of my task"], function() {
console.log("me has been triggered");
});
Whenever I run "grunt watch", I have this output:
Running "watch" task
Waiting...
File "/somepath/WidgetA.js" changed.
Running "newer:mycustomtask" (newer) task
Fatal error: The "newer" prefix is not supported for aliases
I googled but didn't found anything about this. Anyone knows how could I implement this? I need to now in my "customtask" which files have been changed
If you reference a task (inside watch or concurrent e.g.) which is either not installed or not configured you get this error output.
This happens often when you copy-paste a watch config from a different project.
I came across a similar requirement and the solution I ended up with is roughly as follows. Let's assume that the project structure is:
Gruntfile.js
package.json
src/
config.js
data.js
tasks/
customtask.js
Here, the src directory contains data which will be monitored by watch, while the definition of the custom task is stored in tasks/customtask.js. For the purpose of this example, this task will only print the file names of the changed files:
var fs = require('fs');
var path = require('path');
module.exports = function(grunt) {
grunt.registerMultiTask('customtask', function() {
var done = this.async();
if(!this.files){ done(); return; }
this.files[0].src.forEach(file_name => {
console.log(file_name);
});
done();
});
};
Now, Gruntfile.js looks like:
module.exports = function(grunt) {
const files = ['src/config.js', 'src/data.js'];
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
customtask: {
release: {
src: files
}
},
watch: {
data: {
files: files,
tasks: ['customtask:release']
},
options: {
spawn: false
}
}
});
grunt.loadTasks('tasks');
grunt.loadNpmTasks('grunt-contrib-watch');
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config('customtask.release.src', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
grunt.registerTask('build', ['watch:data']);
};
here, it specifies that:
the files of interest are ['src/config.js', 'src/data.js']
that our customtask operates in principle on these files (in case it would be invoked directly)
that watch is supposed to observe these files and launch customtask:release whenever something changes
grunt.loadTasks('tasks') loads all "tasks definitions" from the directory tasks, i.e., here only the customtask
grunt.registerTask('build', ['watch:data']) defines a "shortcut" for watch:data
Finally, in order to invoke customtask only for the changed files, this example uses the strategy employed in the documentation in the section "Compiling files as needed". In loose terms, it assembles all changed files in an object the keys of which are then used to modify the src property of the customtask on-the-fly.
Running grunt build then initiates the "watch". If one runs in another terminal window for example touch src/*.js, the output is:
Running "watch:data" (watch) task
Waiting...
>> File "src/config.js" changed.
>> File "src/data.js" changed.
Running "customtask:release" (customtask) task
src/config.js
src/data.js
where the last two lines come from customtask...
You just need to have a config entry (even an empty one) for your task:
grunt.initConfig({
mycustomtask: {
},
watch: {
widgets: {
files: "/somepath/*.js",
tasks: ['newer:mycustomtask']
}
}
});

How should project-level bundling be handled for non SPA use?

I am learning browserify and I am trying to do two basic things with it:
Transform (via shim) non-CommonJS modules for ease-of-use and dependency tracking
Bundle the libraries that are project-specific
I've found a working process for how to do all of this and automate it with Gulp. This works and produces the right output but, I am curious if it could be made simpler. It seems like I have to duplicate a lot of configuration on the project-based bundles. Here is the working example:
package.json
invalid comments added for clarification
{
//project info and dependencies omitted
//https://github.com/substack/node-browserify#browser-field
"browser": { //tell browserify about some of my libraries and where they reside
"jquery": "./bower_components/jquery/dist/jquery.js",
"bootstrap": "./bower_components/bootstrap/dist/js/bootstrap.js"
},
"browserify": {
//https://github.com/substack/node-browserify#browserifytransform
"transform": [
"browserify-shim"
]
},
"browserify-shim": {
//shim the modules defined above as needed
"jquery": {
"exports": "$"
},
"bootstrap": {
"depends": "jquery:$"
}
}
}
config.js
contains all task-runner related configuration settings
module.exports = {
browserify: {
// Enable source maps and leave un-ulgified
debug: true,
extensions: [],
//represents a separate bundle per item
bundleConfigs: [
{
//I really want to refer to the bundles here made in the package.json but
//if I do, the shim is never applied and the dependencies aren't included
entries: ['/bundles/shared-bundle.js'],
dest: '/dist/js',
outputName: 'shared.js'
}
]
},
//...
};
shared-bundle.js
acts as a bundling file where node loads the dependencies and at this point, the shim has been applied
require('bootstrap');
browserify-task.js
contains the browserify bundling gulp task
//module requires omitted
gulp.task('browserify', function (callback) {
var bundleQueue = config.bundleConfigs.length;
var browserifyBundle = function (bundleConfig) {
var bundler = browserify({
entries: bundleConfig.entries,
extensions: config.extensions,
debug: config.debug,
});
var bundle = function () {
return bundler.bundle()
// Use vinyl-source-stream to make the stream gulp compatible
.pipe(source(bundleConfig.outputName))
// Specify the output destination
.pipe(gulp.dest(bundleConfig.dest))
.on('end', reportFinished);
};
var reportFinished = function () {
if (bundleQueue) {
bundleQueue--;
if (bundleQueue === 0) {
// If queue is empty, tell gulp the task is complete
callback();
}
}
};
return bundle();
};
config.bundleConfigs.forEach(browserifyBundle);
});
In config.js where the first bundleConfig item's entries is a source to a file that has the require() modules, I'd like replace those with module names of modules defined in the package.json browser key.
In the config.js, if I change the bundle configuration to:
bundleConfigs: [
{
entries: ['bootstrap'],
dest: '/dist/js',
outputName: 'shared.js'
}
]
and run the gulp task, it will include bootstrap.js but it doesn't run the shim transformation. jQuery is not being included at all.
This leaves me with a few questions:
Is there a better way to be bundling my js for use in a non-SPA application (ie am I going about this the wrong way)?
If not, is there a way to ensure the shim transformation is run prior to the bundling so that I can have my bundle configuration in one place?
Certainly, you just have to tell your gulp file that it should shim first. Looks like you can add your own shim object when calling browserify from your gulp file. Check out this example
If you want to ensure everything is shimmed before you bundle them, use the deps array: "An array of tasks to be executed and completed before your task will run."
It would look something like this:
gulp.task('shim', function() {
// ...
});
gulp.task('browserify', ['shim'], function(){
// ...
});

Grunt concat files on a different domain or on different server

Edit working version and explanation
I want to concat files from different server into my destination folder using grunt, and grunt-concat and with something like that:
concat: {
options: {
separator: ';'
},
dist: {
src: ['dev.staticcontent.com/media/clientcontent/library/*.js', 'js/*.js'],
dest: 'dist/<%= pkg.name %>.js'
}
},
each time I tried, I received no error from grunt, but my dist/marketing-home.js file is empty... Like it didn't find anything.
Console:
C:\Project\My>grunt
Running "jshint:files" (jshint) task
>> 1 file lint free.
Running "concat:dist" (concat) task
File dist/marketing-home.js created.
Running "uglify:dist" (uglify) task
Done, without errors.
New Version
After the help of Kris, I was able to do it without passing through the web using grunt-exec and doing using COPY or XCOPY shell command.
ex.
exec: {
copy : {
cmd: function () {
var path = "\\\\dev-server123\\WebSites\\Static_Contents\\Media\\clientcontent";
return "copy dist\\*.min.js " + path + " /y";
}
}
}
Doesn't look like concat task handles absolute paths or files from remote locations. However I was able to get it to work using curl task combined with the concat task.
EXAMPLE:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
curl: {
'download/jquery.js': 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js',
},
concat: {
js: {
src: ['download/jquery.js'],
dest: 'output/test.js'
},
}
});
grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['curl', 'concat']);
};
DEMO DIRECTORY STRUCTURE:
I used this Node Module Package for CURL. https://github.com/twolfson/grunt-curl, there may be better ones out there. But this one seemed to work fine.

Categories

Resources