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

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.

Related

Grunt-string-replace not working

I have an app folder where I want to replace http://localhost:8000 for http://fomoapp-melbourne.rhcloud.com in two files: companies-list.component.ts and events-list.component.ts. I am trying to use grunt-replace-string plugin and it seemingly runs successfully with green Done result and no errors, but no replacement happens.
Here is how Gruntfile.js looks like:
module.exports = function(grunt){
[
'grunt-string-replace',
].forEach(function(task){
grunt.loadNpmTasks(task);
});
// configure plugins
grunt.initConfig({
'string-replace': {
dist: {
files: {
'./app/': ['companies-list.component.ts','events-list.component.ts'],
},
options: {
replacements: [{
pattern: 'http://localhost:8000',
replacement: 'http://fomoapp-melbourne.rhcloud.com',
}]
}
}
},
});
// register tasks
grunt.registerTask('default', ['string-replace']);
};
The Grunt Files object is for specifying a mapping of source files to destination files. The purpose of this mapping is to tell Grunt to get the contents of the files in source, do something to the contents, and then write the response to a new file in the destination folder.
It looks to me from your configuration that you want Grunt to rewrite two files in the app/ directory. This is not going to work. I will bet that if you run grunt with the verbose option, grunt --verbose, your output will contain the following:
Files: [no src] -> ./app/
This is because Grunt cannot find the source files because you need to specify their relative paths.
It's up to you how you want to structure your app, but you might want to have a src/ folder and a dist/ folder under app/. If you choose to build your files objects dynamically, your config might look something like this:
files: [{
expand: true,
cwd: './app/src/',
dest: './app/dest/',
src: ['companies-list.component.ts', 'events-list.component.ts']
}]
Additionally, the documentation for grunt-string-replace states:
If the pattern is a string, only the first occurrence will be replaced, as stated on String.prototype.replace.
This means that if you want multiple instances of your string to be replaced, you must provide a Regular Expression literal. For example:
replacements: [{
pattern: /http:\/\/localhost:8000/g,
replacement: 'http://fomoapp-melbourne.rhcloud.com'
}]

google closureCompiler doesn't want to read my goog.getMsg()

I'm trying to compile my js files using closure compiler, but it's giving me this error:
ERROR - goog.getMsg() function could be used only with MSG_* property or variable
my closureCompiler options are:
closureCompiler: {
options: {
compilerFile: 'temp/compiler.jar',
compilerOpts: {
compilation_level: 'ADVANCED_OPTIMIZATIONS',
//compilation_level: 'WHITESPACE_ONLY',
language_in: 'ECMASCRIPT6',
language_out: 'ECMASCRIPT5_STRICT',
formatting: 'PRETTY_PRINT',
externs: ['src/js/compiled/react-extern.js'],
warning_level: 'verbose',
summary_detail_level: 3,
output_wrapper: '"(function(){%output%}).call(window);"',
create_source_map: 'src/js/compiled/output.js.map',
manage_closure_dependencies: true,
use_types_for_optimization: null,
debug: true
},
execOpts: {
maxBuffer: 999999 * 1024
}
},
compile: {
//src: 'src/js/debug/**/*.js',
src: [
'temp/closure-library/closure/goog/base.js',
'src/js/compiled/test.js'
],
dest: 'src/js/compiled/compiled.js'
},
},
I believe I'm missing a flag, but I don't which one to write ?
You can't include goog.getMsg() in your code.
It has to be:
var MSG_SOMETHING = goog.getMsg('something');
and use the MSG_SOMETHING instead.
Google Closure Compiler enforced that, so you could write all your variable in one file and send this one to get translated.

Grunt convert markdown to pdf recursively and dynamically

I am stuck writing a Gruntfile which aim is to convert a bunch of Markdown files to PDF dynamically. Giving the current folder hierarchy:
root/
|_subfolder1
| |_filename1.md
|_subfolder2
|_filename2.md
...
|_node_modules
|_subfolderN
|filenameN.md
I would like to run a Markdown to PDF task which would process the md file and ouput a PDF file with the matching filename in the same output directory.
I did create a custom task which is parsing current directory, ignoring the mode_modules folder and get the markdown file, but I don't know how to configure the md2pdf task with the good properties to reflect dynamic folder mapping.
Here's my current Gruntfile:
module.exports = function(grunt) {
// 1 - Configuration
grunt.initConfig({
md2pdf: {
}
});
// 2 - Plugins
grunt.loadNpmTasks('grunt-md2pdf');
// 3 - Task registering
grunt.registerTask('default', 'Get Subfolders', function() {
grunt.file.recurse('.', callback);
function callback(abspath, rootdir, subdir, filename) {
var filenameOutExt;
// if current occurence is a file subdir == undefined
// checking subdir to true means it's not undefined and
// the current path is a directory
if(subdir) {
// excluding node_modules folder
if (!subdir.match('node_modules')) {
// only process markdown files
if(filename.match('.md')) {
filenameOutExt = filename.split('.')[0];
// now for each markdown files, run md2pdf task
// and ouput filenameOutExt.pdf in same folder
// as the input files
}
}
}
}
});
};
I am using this plugin: https://www.npmjs.com/package/grunt-md2pdf
So my questions is how should I configure the md2pdf task to pass it the markdown files and generate matching filename pdf output in same directory.
Output should be:
root/
|_subfolder1
| |_filename1.md
|_filename1.pdf
|_subfolder2
|_filename2.md
|_filename2.pdf
...
|_node_modules
|_subfolderN
|filenameN.md
|_filenameN.pdf
Thanks a lot
According to this line, this task uses the grunt.files utilitary function. This makes things easier for us!
First, it's not usual in grunt to create a different task to find the files you need in any other task.
That is, each task should receive the files it needs to operate on. For example...
coffee:
main:
files: [
expand: true
cwd: 'assets/script'
src: ['**/*.coffee']
dest: "assets/script"
ext: '.js'
]
(Note that this config is in a Gruntfile.coffee file, hence the CoffeeScript syntax)
This type of file configuration using glob expansion is one of the most common. You can find details in the documentation.
It's pretty obvious:
In every directory (**), take everything (/*) from assets/script/ that ends with .coffee. Put it into assets/script. Rename extensions to .js.
So, your task can probably be configured like that:
md2pdf: {
main: {
files: [ {
expand: true,
src: ['**/*.md', '!node_modules/**/*'],
dest: "pdf/"
} ]
}
}
Ok figured it out !
module.exports = function(grunt) {
// 2 - Plugins
grunt.loadNpmTasks('grunt-md2pdf');
grunt.registerTask('default', 'Dynamically generate PDF from MD', function() {
grunt.file.expand("./**/*.md").forEach( function(file) {
if(!file.match('./node_modules')) {
var md2pdf = grunt.config.get('md2pdf') || {};
md2pdf[file] = {
src: file,
dest: file + '.pdf'
};
grunt.config.set('md2pdf', md2pdf);
}
});
grunt.task.run('md2pdf');
});
};

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.

Use Grunt task variables in config

I'd like to define variables in the config of my Grunt task, which are being replaced by my task.
My use-case is that I'm trying to create separate JS output files, based on a config.json which contains configurations for multiple sites.
An example of my config:
extractProjectConfigs: {
options: {
merge: '<%= yeoman.dist %>/extracted.js',
configWrapper: 'window.config = {{ extractedConfig }};'
},
prod: {
src: ['./config/*.json'],
dest: '.tmp/scripts/{{ configKey }}/searchbox.js'
}
}
In my extractProjectConfigs task, I define the variables configKey and extractedConfig and I'd like the values of both to be in my config.
How can I achieve this? I already tried including Grunt Templates in my config definition (i.e. <%= extractedConfig %> or <%= configKey %>), but since Grunt Template parses variables before passing them to my task, it basically means my variables turn into empty strings.
Turned out Grunt Templates were the right way to go. Grunt Templates parse variables defined by <%= %> before passing them to the task. However, Grunt Templates also offer a way to determine your own variable delimiter, which allows you to use Grunt Templates in your task that are parsed after the task ran, meaning I can use variables in my config.
I ended up using the following config:
extractProjectConfigs: {
options: {
merge: '<%= yeoman.dist %>/extracted.js',
configWrapper: 'window.config = {{= extractedConfig }};'
},
prod: {
src: ['./config/*.json'],
dest: '.tmp/scripts/{{= configKey }}/searchbox.js'
}
}
In my task, I used the following custom Grunt template function to parse the config:
function parseTemplate(template, vars) {
grunt.template.addDelimiters('double-brackets', '{{', '}}');
return grunt.template.process(template, {
data: vars,
delimiters: 'double-brackets'
});
}
This function can then be used within the task, for instance as follows:
var dest = parseTemplate(file.dest, {
projectKey: 'foo'
});
This can then be used within grunt.file.write: grunt.file.write(dest, 'write something to dest with projectKey replaced by foo');

Resources