I have a custom task:
grunt.registerTask('testtask', 'Test Task', function() {
....
I call on default:
grunt.registerTask('default', ['testtask']);
But I want to be able to get a var from config inside the task, dependent if the task was called by grunt or grunt deploy.
So for grunt I was to get a path var on the local system and on grunt deploy I want an external path.
So inside the task I want to just be able to call something like this.data.path and dependent on whether deploy was called, the path will vary.
Grunt exposes this.name, which you can use to check which task was specifically invoked. In the example below, I've created a function buildTask(), and assigned it to two different tasks, default, and deploy. Within the build task, I'm using a ternary operator to set the path to one of two values, based on whether this.name matches deploy:
function buildTask() {
// if this.name matches 'deploy', set var path to 'foo/bar', else set to 'foo/baz'
var path = (this.name === 'deploy') ? 'foo/bar' : 'foo/baz';
// your code goes here
}
grunt.registerTask('default', buildTask); // path = "foo/baz"
grunt.registerTask('deploy', buildTask); // path = "foo/bar"
Related
So lets say I have some code in js
const myApiKey = 'id_0001'
But instead of harcoding it I want to put it in some bash script with other env vars and read from it and then replace it in the JS
So lets say for prod I would read from prod-env.sh or for dev I would read them from dev-env.sh and then gulp or some other tool does the magic and replaces MY_API_KEY based on whatever is established inside of prod-env.sh or dev-env.sh.
const myApiKey = MY_API_KEY
Update: I want to add I only care about unix OS, not concerned about windows. In golang there is way to read for example envVars.get('MY_API_KEY'), I'm looking for something similar but for JS in the client side.
If you're using gulp, it sounds like you could use any gulp string replacer, like gulp-replace.
As for writing the gulp task(s). If you are willing to import the environment into your shell first, before running node, you can access the environment via process.env
gulp.task('build', function(){
gulp.src(['example.js'])
.pipe(replace('MY_API_KEY', process.env.MY_API_KEY))
.pipe(gulp.dest('build/'));
});
If you don't want to import the environment files before running node, you can use a library like env2 to read shell environment files.
Another option would be to use js/json to define those environment files, and load them with require.
prod-env.js
{
"MY_API_KEY": "api_key"
}
gulpfile.js
const myEnv = require('./prod-env')
gulp.task('build', function(){
gulp.src(['example.js'])
.pipe(replace('MY_API_KEY', myEnv.MY_API_KEY))
.pipe(gulp.dest('build/'));
});
Also, for a more generic, loopy version of the replace you can do:
gulp.task('build', function () {
stream = gulp.src(['example.js']);
for (const key in process.env) {
stream.pipe('${' + key + '}', process.env[key]);
}
stream.pipe(gulp.dest('build/'));
});
In that last example I added ${} around the environment variable name to make it less prone to accidents. So the source file becomes:
const myApiKey = ${MY_API_KEY}
This answer is an easy way to do this for someone who doesn't want to touch the code they are managing. For example you are on the ops team but not the dev team and need to do what you are describing.
The environment variable NODE_OPTIONS can control many things about the node.js runtime - see https://nodejs.org/api/cli.html#cli_node_options_options
One such option we can set is --require which allows us to run code before anything else is even loaded.
So using this you can create a overwrite.js file to perform this replacement on any non-node_modules script files:
const fs = require('fs');
const original = fs.readFileSync;
// set some custom env variables
// API_KEY_ENV_VAR - the value to set
// API_KEY_TEMPLATE_TOKEN - the token to replace with the value
if (!process.env.API_KEY_TEMPLATE_TOKEN) {
console.error('Please set API_KEY_TEMPLATE_TOKEN');
process.exit(1);
}
if (!process.env.API_KEY_ENV_VAR) {
console.error('Please set API_KEY_ENV_VAR');
process.exit(1);
}
fs.readFileSync = (file, ...args) => {
if (file.includes('node_modules')) {
return original(file, ...args);
}
const fileContents = original(file, ...args).toString(
/* set encoding here, or let it default to utf-8 */
);
return fileContents
.split(process.env.API_KEY_TEMPLATE_TOKEN)
.join(process.env.API_KEY_ENV_VAR);
};
Then use it with a command like this:
export API_KEY_ENV_VAR=123;
export API_KEY_TEMPLATE_TOKEN=TOKEN;
NODE_OPTIONS="--require ./overwrite.js" node target.js
Supposing you had a script target.js
console.log('TOKEN');
It would log 123. You can use this pretty much universally with node, so it should work fine with gulp, grunt, or any others.
I'm extremely new to Gulp. I'm basically trying to watch for a modified JavaScript file, and then make a new copy of it with a new name. (eventually there'll be some processing on it, but Rome wasn't built in a day).
My (naive) attempt is this:
gulp.task('default', function() {
return gulp.watch('../**/**.js', function(obj){
gulp.src(obj.path)
.pipe(gulp.dest('foobar.js'));
});
});
This takes the modified file and successfully copies it into a folder now called foobar.js. Is there anything simple I can replace gulp.dest('foobar.js') with that will simply copy and rename the src file in place?
EDIT
By copy in place, I mean I want to take the modified file, and make a copy of it right where it currently is with a new name. The equivalent of clicking the file (in windows) and hitting control-c control-v, then renaming the resulting file.
I'm not 100% certain what you mean by
copy and rename ... in place
But, based on your current code, if you simply wish to:
Watch all .js files in the parent directory and
Copy them to the cwd (current working directory) and
Name all copies, regardless of source file, the same thing
Then you could use gulp-rename to do just that:
var gulp = require('gulp');
var rename = require('gulp-rename');
gulp.task('default', function() {
return gulp.watch('../**/**.js', function(obj) {
gulp.src(obj.path)
.pipe(rename('newFileName.js'))
.pipe(gulp.dest('.'));
});
});
In this case, the output filename is newFileName.js
In order to use the module, you'll need to install the gulp-rename package with npm (ie: npm install gulp-rename).
More examples are available on the package details page on npm # https://www.npmjs.com/package/gulp-rename#usage
It wasn't pretty getting there, but in the end it appears this is what I want (with some ES6 transpiling in the middle).
The key appears to be the options object with a base property in the call to src. That seems to be what's needed to maintain the path of the current file in the call to dest.
var gulp = require('gulp'),
rename = require('gulp-rename'),
babel = require('gulp-babel');
gulp.task('default', function() {
return gulp.watch('../**/$**.js', function(obj){
if (obj.type === 'changed') {
gulp.src(obj.path, { base: './' })
.pipe(babel())
.pipe(rename(function (path) {
path.basename = path.basename.replace('$', '');
}))
.pipe(gulp.dest(''));
}
});
});
20 lines of code to do 'cp file1 file2'
That's elegance.
I am using grunt-contrib-concat and I have a simple concat task / config like below
concat: {
options: {
sourceMap: true
},
vendor: {
src:['lib/**/*.js'],
dest: 'dist/scripts/vendor.js'
},
app: {
src:['app/**/*.js'],
dest: 'dist/scripts/app.js'
}
}
So when I am running above task through console I would like to be able to specify enable / disable sourceMap generation. Source map generation can take forever.
I tried below but none worked.
grunt concat:vendor --sourceMap=false
grunt concat --sourceMap=false
Thanks.
I know one way to do this, it requires you to write a custom task, it's easy.
// Leave your `concat` task as above
concat: ...
// and then define a custom task as below (out of `grunt.config.init` call)
grunt.registerTask('TASK_NAME', 'OPTIONAL_DESCRIPTION', function (arg) {
// CLI can pass an argument which will be passed in this function as `arg` parameter
// We use this parameter to alter the `sourceMap` option
if (arg) {
grunt.config.set('concat.options.sourceMap', false);
}
// Just run `concat` with modified options or pass in an array as tasks list
grunt.task.run('concat');
});
This is simple, you can customize this template as your wishes.
To use it, just use a ":" to pass extra parameter(s) in CLI like below:
$ grunt concat:noSrcMap
Basically you can pass anything as the parameter, it will be treated like a string (or undefined if no parameter passed).
So here is my hypothetical config object for a hypothetical fooTask that does something (not relevant to question) to a bunch of JS files
grunt.initConfig({
fooTask: {
app1: 'app1/*.js',
app2: 'app2/*.js',
app3: 'app3/*.js'
}
});
As you can see, with this approach, I have to run fooTask 3 times with each app specified as a target:
grunt fooTask:app1
grunt fooTask:app2
grunt fooTask:app3
Needless to say this does not scale as either the number of apps increase or the number of such foo tasks increase as one has to C&P the same code over and over for each app.
So ideally what I would like to define is just one target with the name of the app passed in as a config variable
grunt.initConfig({
fooTask: {
dist: '<%=appName%>/*.js'
}
});
I would then like to call fooTask 3 times, one for each app, with the right app set as appName
var apps = ['app1', 'app2', 'app3'];
apps.forEach(function(app) {
var currAppName = app;
// Run fooTask but how do I specify the new currAppName config?
grunt.task.run('fooTask');
});
As from code above, I know I can run my fooTask using grunt.task.run but how do I set the appName config for my task?
Note that this question is similar to this other one that also does not have the right answer yet - Pass Grunt config options from task.run
Thanks a lot.
EDIT 2:
So nevermind the garbage below the first edit, leaving as example of what doesn't work. In my case it was really important to be able to set the value within a task at run-time so I settled on the file system. Perhaps it suits your needs.
grunt.initConfig({
someTask: {
someKey: fs.readFileSync('file.txt', { encoding: 'utf8' })
}
});
of course you can do the readFile outside of the task if you need a bunch of different app names.
EDIT:
Hmmm. I swear I had this working when I wrote this...but now it is not. Grunt just sees the extra arguments as additional unfound tasks.
I was trying to figure this out myself, finally a "duh" just moment happened - why not parse process.argv before grunt.initConfig?
module.exports = function(grunt) {
var sourcefile = process.argv[2] || 'default.js'; // <- this
grunt.initConfig({
uglify: {
main: {
src: sourcefile, // <- voila :)
dest: sourcefile.substring(0, sourcefile.length-3) + '.min.js'
}
}
});
grunt.loadNpmTasks('uglify');
grunt.registerTask('default', ['uglify']);
};
and use from command line:
grunt mykillerscript.js
I didn't even try to use grunt.option for the same reason that all the examples only showed directing which task is run, but I wouldn't be surprised if there is a more "grunt" way to do this.
I have multiple sets of js modules that I would like to concat into separate files. I don't want to have to create a seperate concat task for each file. It would make more sense to be able to pass arguments into the gulp task "concat". Unfortunately gulp doesn't allow arguments to be passed into tasks(I'm sure for good reason).
Any ideas of how I can accomplish this?
Use Case
A specific scenario would be website that has a global.js file for all pages as well as page specific js files.
Creating a task for each page specific js file will quickly make the gulpfile.js hard to manage as the site grows.
My dev invironment:
I have a dev/js/ directory which has multiple sub-directories. Each sub-directory contains modules for a specific js file. So each sub-directory needs to be concatenated into it's own file within lib/js/.
Perhaps requirejs?
Maybe I should just look into using a module loader like requirejs.
I needed to take modules from my source sub-directory (src/modules/), concatenate a specific file to each individually (src/distribution), then pipe the result to a sub-directory in my distribution folder (dist/js/modules/).
I wasn't sure how many modules would end up being written for this project so I wanted to do it dynamically and found this to be the best (simplest) solution:
gulp.task("modules:js", () => {
let modules = fs.readdirSync("src/modules");
let concatModule = (module) => {
return gulp.src([
'src/distribution',
module
])
.pipe(concat(module))
.pipe(gulp.dest("build/js/modules"));
}
for (let module of modules) {
concatModule(module);
};
});
You could make concatJS a higher-order function:
var concatJS = function (src, filename, dest) {
return function() {
gulp.src(src)
.pipe(concat(filename))
.pipe(gulp.dest(dest));
};
};
gulp.task('concat-1', concatJS('src/module-1', 'module-1.js', 'build/module-1'));
gulp.task('concat-2', concatJS('src/module-2', 'module-2.js', 'build/module-2'));
//etc...
Note: You'd probably be better off using a bundler like browserify or webpack. Since asking this question I have switched to browserify rather than trying to roll my own solution.
Improved Solution:
var fs = require("fs");
/* other requires omitted */
/* Set JS dev directory in one place */
var jsSrc = "dev/js/";
var jsDest = "lib/js/";
var concat = function (path) {
path = path.replace(/\\/g, "/");
var src = path.replace(/(\/[^\/]+?\.js$)|(\/$)/, "/*.js";
var filename = src.match(/\/([^\/]+?)(\/[^\/]+?\.js$)/)[1] + ".js";
gulp.src(src)
.pipe(concat(filename)
.pipe(gulp.dest(jsDest));
});
/* The concat task just runs the concat function for
* each directory in the javascript development directory.
* It will take a performance hit, but allows concat to be
* run as a dependency in a pinch.
*/
gulp.task("concat", function () {
var dirArr = fs.readdirSync(jsDev);
for (var d in dirArr) {
var path = jsDev+dirArr[d]+"/";
concat(path);
}
});
/* Run "concat" as a dependency of the default task */
gulp.taks("default", ["concat"], function () {
var JSWatcher = gulp.watch([jsSrc+"**/*.js"]);
JSWatcher.on("change", function (event) {
concat(event.path);
});
});
Alright, I think this works. It's a little bit of a hack though, and doesn't work for all use cases.
... removed previous example to save space ...