I'm looking to create multiple HTML files from a single Jade template using Grunt.
Here's what I'm doing:
Grabbing the JSON data from an external file
Looping through that object
Creating a grunt config task for each value in that JSON object
Here's my code:
neighborhoods = grunt.file.readJSON('data/neighborhoods.json');
for(var i = 0; i < Object.keys(neighborhoods).length; i++) {
var neighborhood = {
"title" : Object.keys(neighborhoods)[i],
"data" : neighborhoods[Object.keys(neighborhoods)[i]]
};
grunt.config(['jade', neighborhood.title], {
options: {
data: function() {
return {
neighborhoods: neighborhood.data
}
}
},
files: {
"build/neighborhoods/<%= neighborhood.title %>.html": "layouts/neighborhood.jade"
}
});
}
The problem that I'm running in to is this
Running "jade:Art Museum" (jade) task
Warning: An error occurred while processing a template (Cannot read property 'title' of undefined). Use --force to continue.
If I make the filename a string, it runs fine but obviously creates all the files with the same filename, thus only creating one file. I need to make that filename dynamic.
I found the solution here:
Use Global Variable to Set Build Output Path in Grunt
The issue is that the module exports before those global variables get set, so they are all undefined in subsequent tasks defined within the initConfig() task.
This did the trick!
var neighborhoods = grunt.file.readJSON('data/neighborhoods.json');
for(var i = 0; i < Object.keys(neighborhoods).length; i++) {
var neighborhood = {
"title" : Object.keys(neighborhoods)[i],
"data" : neighborhoods[Object.keys(neighborhoods)[i]]
};
/*
* DEFINE VALUE AS GRUNT OPTION
*/
grunt.option('neighborhood_title', neighborhood.title);
grunt.config(['jade', neighborhood.title], {
options: {
data: function() {
return {
neighborhoods: neighborhood.data,
neighborhood_title: neighborhood.title
}
}
},
/*
* OUTPUT GRUNT OPTION AS FILENAME
*/
files: {
"build/neighborhoods/<%= grunt.option('neighborhood_title') %>.html": "layouts/neighborhood.jade"
}
});
}
This results in the desired output:
Running "jade:East Passyunk" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Fishtown" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Graduate Hospital" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Midtown Village" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Northern Liberties" (jade) task
File build/neighborhoods/Society Hill.html created.
...
I know this is an old post but I kept coming back here whilst trying to solve a similar problem. I wanted to output multiple html files from a single jade template file using a for-loop.
The two problems I came across was setting the output filename (a javascript object literal KEY) and making sure inline javascript functions are run immediately so that the loop variables are available.
Here is my full source code with comments. I hope this is of use to anyone else stumbling across this post.
Gruntfile.js:
module.exports = function(grunt) {
// Create basic grunt config (e.g. watch files)
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
grunt: { files: ['Gruntfile.js'] },
jade: {
files: 'src/*.jade',
tasks: ['jade']
}
}
});
// Load json to populate jade templates and build loop
var json = grunt.file.readJSON('test.json');
for(var i = 0; i < json.length; i++) {
var obj = json[i];
// For each json item create a new jade task with a custom 'target' name.
// Because a custom target is provided don't nest options/data/file parameters
// in another target like 'compile' as grunt wont't be able to find them
// Make sure that functions are called using immediate invocation or the variables will be lost
// http://stackoverflow.com/questions/939386/immediate-function-invocation-syntax
grunt.config(['jade', obj.filename], {
options: {
// Pass data to the jade template
data: (function(dest, src) {
return {
myJadeName: obj.myname,
from: src,
to: dest
};
}()) // <-- n.b. using() for immediate invocation
},
// Add files using custom function
files: (function() {
var files = {};
files['build/' + obj.filename + '.html'] = 'src/index.jade';
return files;
}()) // <-- n.b. using () for immediate invocation
});
}
grunt.loadNpmTasks('grunt-contrib-jade');
grunt.loadNpmTasks('grunt-contrib-watch');
// Register all the jade tasks using top level 'jade' task
// You can also run subtasks using the target name e.g. 'jade:cats'
grunt.registerTask('default', ['jade', 'watch']);
};
src/index.jade:
doctype html
html(lang="en")
head
title= pageTitle
script(type='text/javascript').
if (foo) {
bar(1 + 5)
}
body
h1 #{myJadeName} - node template engine
#container.col
p.
Jade is a terse and simple
templating language with a
strong focus on performance
and powerful features.
test.json:
[{
"id" : "1",
"filename" : "cats",
"tid" : "2016-01-01 23:35",
"myname": "Cat Lady"
},
{
"id" : "2",
"filename" : "dogs",
"tid" : "2016-01-01 23:45",
"myname": "Dog Man"
}]
After running 'grunt' the output should be:
build/cats.html
build/dogs.html
Came across a similar requirement for a project I'm working on but couldn't get this to work. I kept getting only one file generated since the grunt option had same value for all tasks (the last value). So I ended up using <%= grunt.task.current.target %> for the file name which in your case would be same as neighborhood.title.
Related
I want to create a Gulp watcher for every template i create for my project.
The templates are located in my templates folder.
I'm using the following code to create my templates:
let javascriptTemplates = {
0: { folder: 'template-1' },
1: { folder: 'template-2' },
};
And the following to create the watchers:
gulp.task('start', function () {
for(let i = 0; i < javascriptTemplates .length; i++) {
let folder = javascriptTemplates[i]['folder'];
let templateDirectory = `${javascriptTemplateDirectory}/${folder}`;
let templateWatcherDirectory = `${templateDirectory}/**/*.js`;
gulp.watch([templateWatcherDirectory], formatJavascriptFiles(templateWatcherDirectory, folder));
}
});
Is this possible at all with Gulp watchers?
I'm struggling to make it work but can't get it right.
The watcher starts and the formatJavascriptFiles function works just fine.
When i save any javascript file the watcher wont start and no files will be minified.
If someone know if this is possible and how i can fix my issue.
I have several typescript files, some of them export a const named APIS.
I'm trying to access those exports (I want to concatenated all of them to a single file), but it doesn't seem to work. I'm obviously doing something wrong, but I'm not sure what.
For example, I have a folder named services, with 2 files: service1.ts, service2.ts.
service1.ts:
...
export const APIS = [ { "field1" : "blabla" } ];
service2.ts: does not contain the APIS var.
This is my gulpfile.js:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
gulp.task('default', function() {
return gulp.src('.../services/*.ts')
.pipe(map(function(file) {
return file.APIS;
}))
.pipe(concat('all.js'))
.pipe(gulp.dest('./test/'));
});
When I run this task, I get nothing. When I added console.log(file.APIS); to the map function, I get undefined for all the values (although it is defined in service1.ts!).
This is following to: Extracting typescript exports to json file using gulp
EDIT: OK, so I tried saving the exports in a .js file instead of a .ts file, and now I can access those vars using require:
gulp.task('default', function() {
return gulp.src('./**/*.service.export.js')
.pipe(map(function(file) {
var fileObj = require(file.path);
...
}))
Now if I try console.log(fileObj.APIS); I get the correct values. What I'm still confused about is how I can pass these value on, and create a single file out of all these vars. Is it possible to push them into an array?
This will not work as you think it would work. Gulp itself knows nothing about typescript files, that file is a vinyl-file and has no knowledge about the typescript code within its content.
Edit
Based on your example, you can do something like this:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
var fs = require('fs');
gulp.task('test', function ()
{
var allConstants = [];
var stream = gulp.src('./**/*.service.export.js')
.pipe(map(function(file)
{
var obj = require(file.path);
if (obj.APIS != null)
allConstants = allConstants.concat(obj.APIS);
return file;
}));
stream.on("end", function (cb)
{
// Do your own formatting here
var content = allConstants.map(function (constants)
{
return Object.keys(constants).reduce(function (aggregatedString, key)
{
return aggregatedString + key + " : " + constants[key];
}, "");
}).join(", ");
fs.writeFile('filename.txt', content, cb);
});
return stream;
});
Suggestion
If you want to collect multiple variables into a single file i.e. a common variables file I suggest gulp-replace.
Steps
Create a file, require it and use tags within that file to place your variables.
Advice
If you are already using services don't create an array. Instead create an object (JSON) where every property is a constant. i.e.
var constants = {
const_1: 0,
const_2: 1,
const_3: 2,
}
I have several typescript files, some of them export a certain var - named APIS - which is an array of objects.
I want to extract the values of all of these exports, and pipe them to a json file using gulp.
For example, I have a folder named services, with 3 files: service1.ts, service2.ts, service3.ts.
service1.ts:
...
export const APIS = [ { "field1" : "blabla" } ];
service2.ts:
...
export const APIS = [ { "field2" : "yadayada" }, { "field3" : "yadabla" } ];
service3.ts: - does not export the APIS var.
I want to use gulp in oder create a json file that looks something like this:
[ { "field1" : "blabla" }, { "field2" : "yadayada" }, { "field3" : "yadabla" } ]
gulpfile.js - the ??? is a placeholder for the missing code.
gulp.task('default', function () {
return gulp.src('.../services/*.ts')
.pipe(???)
.pipe(concat('export.json'))
.pipe(gulp.dest('./test'));
});
I'm new to both typescript & gulp, so I'm not sure how to achieve this... any ideas? :)
EDIT: So, I understand that there's no OOTB solution for this, and I need to writer my own task / plugin. I'm not really sure how to achieve that, though.
Ideally, what I want is to find a gulp plugin (or a combination of plugins) that can handle ts / js files as objects with properties. Then I can extract the var I need from the file.
I couldn't really find something like that, only string manipulation plugins - Treating my ts file as a string and using search with regex seems overly complicated to me. Is there something I'm missing here? is there a more straight-forward way to do this?
The typescript compiler API is relevant here, as this is what you need to parse and understand the ts-code properly. Unfortunately, I don't think there is a gulp plugin that implements this API.
I think your best bet is to change strategy completely here and solve your problem in another way, or to use regex to try to extract the constants that you want. Unless you want to write your own gulp-plugin using the compiler API.
This is what I ended up doing, and it worked for me. I'm positing it here in case anyone else finds it useful. :)
Instead of .ts, I saved the exports in .js files, i.e:
service2.export.js:
exports.APIS = [ { "field2" : "yadayada" }, { "field3" : "yadabla" } ];
Based on the answer given here: https://stackoverflow.com/a/36869651/3007732 I created a gulp task as following:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
var fs = require('fs');
var allServices;
gulp.task('default', function() {
var allServices = [];
var stream = gulp.src('./**/*.export.js')
.pipe(map(function(file) {
var obj = require(file.path);
if (obj.APIS != null) {
allServices.push.apply(allServices, obj.APIS);
}
return file;
}));
stream.on("end", function (cb)
{
fs.writeFile('./export.json', JSON.stringify(allServices), cb);
});
return stream;
});
and now I get the following output in export.json:
[ { "field1" : "blabla" }, { "field2" : "yadayada" }, { "field3" : "yadabla" } ]
which is exactly what I wanted.
I have a project where I have to generate translated static pages.
The choice was to use gulp because it helps a lot in minifying resources, watch for file changes and re-compile, and can also inject html templates in several pages.
I used:
- 'gulp-inject': for inserting templates into final files
- 'gulp-translate-html': for translating because I have '.json' dictionaries
So I have two issues:
'gulp-translate-html'
This uses the json as input for translating, using the following code:
gulp.task('translate', function() {
return gulp.src('./temp/en/template.html')
.pipe(translateHtml({
messages: require('./dictionary/en.json'),
templateSettings: {
interpolate: /{{([\s\S]+?)}}/g
}
}))
.pipe(gulp.dest('./en'));
});
I created a watch on the '.json' file, when modified, it should re-apply the translation. But somehow it uses the old file instead of the modified one.
Is there a workaround for this? Or other plugin that I could use for the json files?
'gulp-inject'
In the code-sample above, I translated only one file. But I need to do so for several languages that have different destinations, so I used a loop for the languages.(sorry for the code indentation)
var gulp = require('gulp'),
inject = require('gulp-inject'),
translateHtml = require('gulp-translate-html');
var languages = ['en', 'de'];
gulp.task('injectContent', function() {
/* the base file used as a reference*/
var target = gulp.src('./templates/base/baseTemplate.html');
/* get each language*/
languages.forEach(function(lang) {
target.pipe(inject(gulp.src('./templates/partials/injectTemplate.html'), {
relative: true,
starttag: '<!-- inject:template -->',
transform: function (filePath, file) {
return file.contents.toString('utf8');
}
}))
/* put the merged files into "temp" folder under its language folder*/
.pipe(gulp.dest('./temp/'+lang));
});
});
/* The translation has to be made after the injection above is finished*/
gulp.task('translate', ['injectContent'] function() {
/* get each language*/
languages.forEach(function(lang) {
gulp.src('./temp/'+ lang +'/baseTemplate.html')
.pipe(translateHtml({
messages: require('./dictionary/'+lang+'.json');,
templateSettings: {
interpolate: /{{([\s\S]+?)}}/g
}
}))
.pipe(gulp.dest('./'+lang)); /* put file in the "en" or "de" language folder*/
});
});
gulp.task('watch', function() {
gulp.watch(['./templates/**/*.html', './dictionary/*.json'], ['translate']);
});
gulp.task('default', ['translate', 'watch']);
Here I want the 'injectContent' task to be ran before the 'translation' task, but the latter runs too soon. This happens because there is not a specific return gulp callback in the 'injectContent', right?
How can I merge the results and not let the tasks intercalate?
Just found a solution for the caching issue from point 1:
Based on this answer: https://stackoverflow.com/a/16060619/944637 I deleted the cache and then the "require" function could reload the file from the filesystem and not from cache.
I added delete require.cache[require.resolve('./dictionary/en.json')]; at the beginning of the 'translate' task, before return.
EDIT: Just found the solution on Point 2 to merge the results using "merge-stream", in this answer here: https://stackoverflow.com/a/26786529
so my code turned out to be like this:
....
merge = require('merge-stream');
gulp.task('injectContent', function() {
var tasks = languages.map(function(lang){
return gulp.src('./templates/base/injectContent.html')
.pipe(plumber())
.pipe(debug())
.pipe(inject(gulp.src('./templates/partials/injectTemplate.html'), {
relative: true,
starttag: '<!-- inject:release -->',
transform: function (filePath, file) {
return file.contents.toString('utf8');
}
}))
.pipe(gulp.dest('./temp/'+lang));
});
return merge(tasks);
});
gulp.task('translate', ['injectContent'], function() {
for (var i in languages) {
var lang = languages[i];
delete require.cache[require.resolve('./dictionary/'+lang+'.json')];
gulp.src('./temp/'+lang+'/injectContent.html')
.pipe(plumber())
.pipe(debug())
.pipe(translateHtml({
messages: require('./dictionary/'+lang+'.json'),
templateSettings: {
interpolate: /{{([\s\S]+?)}}/g // this is for Angular-like variable syntax
}
}))
.pipe(gulp.dest('./'+lang));
}
});
....
The project is: Backbone + Require + Underscore + Grunt + Grunt-Contrib-Jasmine + Grunt-Lib-PhantomJS
So two serious problems I've been battling. I know that phantomjs is running properly etc. as I get tons of runtime errors if I include my app src files. I've even ordered the deps properly such that Backbone does not barf with _ not being defined etc.
1) When I include my application src, I get the error can't find variable: define for all my source files. I've tried putting requirements into src[] isntead of vendor[] and even tried loading a RequireJSConfig.js that has the deps in it.
2) Here's the cruncher: I'm pretty certain I'm pointing at my spec files properly. If I just point to one test, it still says No Specs Executed. Is there a configuration error? In my case, I just point at my UserModelUnitTest.js, which is very simple. It does not execute. I'm going absolutely nuts!
The Spec (UserModelUnitTest.js):
describe('User Model Unit Tests', function() {
var USER_MODEL,
USER_CLASS,
JSON_OBJ;
beforeEach(function() {
USER_CLASS = testr('models/user/User', {});
});
afterEach(function() {
USER_MODEL = null;
USER_CLASS = null;
JSON_OBJ = null;
});
describe('Given a json object', function() {
it('should create a valid User', function() {
JSON_OBJ = {"databaseId": 123456,"loginName": "god","firstName": "Jesus","lastName": "Christ","phone": "666-666-6666","email": "satan#hell.org","isoCountryCode": "US","languageCode": "en","roles" : ["SALES_REP"]};
USER_MODEL = new USER_CLASS(JSON_OBJ, { parse: true });
expect(USER_MODEL).not.toBe(null);
});
// etc...
});
})
Here's my dir structure
/project
- src
- main
+ test
+ js
+unit
UserModelUnitTest.js
Here's my Gruntfile / Jasmine config
jasmine: {
test:{
vendor:[
'src/main/resources/js/lib-clean/jquery-2.1.0.js',
'src/main/resources/js/lib-clean/require-2.1.1.full.js',
'src/main/resources/js/lib-clean/underscore-1.5.2.min.js',
'src/main/resources/js/lib-clean/backbone-1.1.2.min.js'
],
src : [
// these all error like crazy. Can't find variable 'define' etc.
// 'src/main/**/*.js',
// 'src/main/**/**/*.js',
//'src/test/RequireJSConfig.js'
],
helpers : [
'src/test/js/helpers/dependencyHelper.js',
'src/test/js/helpers/errorHelper.js',
'src/test/js/helpers/requesetHelper.js',
'src/test/lib/testr.js',
// jasmine.js + jasmine-html.js etc
'src/test/lib/*.js',
// stubs
'src/test/js/stubs/*.js'
],
specs : [
'src/test/js/unit/UserModelUnitTest.js'
],
//specs : 'src/test/js/unit-headless.html',
timeout : 10000,
phantomjs : {
'ignore-ssl-errors' : true
}
}
},
I just had the same problem. You need to define vendor, specs, helpers within the options option.
jasmine: {
src: 'path/to/src',
options: {
vendor: 'path/to/vendor',
specs: 'path/to/specs',
helpers: 'path/to/specs'
// etc.
}
}
sometimes happen because: you did not created the spec folder and the spec file, also when you create the spec file you need create the test inside or will not run.