Using grunt js, how can I dynamically add the <title> to static html files?
Here is my take on this. Uses grunt's standard template mechanism, page metadata is defined in an object, outside of actual page files, as you suggested (I can't say I like this).
gruntfile (including the wrap
task from Using grunt concat, how would I automate the concatenation of the same file to many other files?):
/*global module:false*/ module.exports = function(grunt) { // Project configuration. grunt.initConfig({ meta: { version: '0.1.0', banner: '/*! PROJECT_NAME - v<%= meta.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '* http://PROJECT_WEBSITE/\n' + '* Copyright (c) <%= grunt.template.today("yyyy") %> ' + 'YOUR_NAME; Licensed MIT */' }, // Paths project: { partials: 'assets/partials', // don't put trailing slash pages: 'assets/pages', // don't put trailing slash less: 'assets/less', css: 'assets/css', img: 'assets/img', js: 'assets/js' }, // Used for page title and nav generation. // It's an array to ensure correct order for nav pages: [{ file: 'index.html', title: 'My homepage' /* This format can be extended to something like: * { * title: 'My homepage', * header: 'Welcome to my site', * navtitle: 'Home' * } * Although I think it's best to keep page metadata as close to content as possible, * i.e. right inside pages files (think YAML headers in Jekyll pages) */ }, { file: 'about.html', title: 'About me' }, { file: 'contact.html', title: 'Contact' }], // wraps files with header and footer wrap: { html: { header: '<%= project.partials %>/head.tmpl', footer: '<%= project.partials %>/footer.tmpl', src: [ '<%= project.pages %>/index.html', '<%= project.pages %>/about.html', '<%= project.pages %>/contact.html' ], dest: '.' // destination *directory*, probably better than specifying same file names twice } }, // processes templates in page files buildPages: { pages: '<config:pages>', // page files metadata dir: '.' // page files location dir } }); // Default task. grunt.registerTask('default', 'wrap buildPages'); grunt.registerMultiTask('wrap', 'Wraps source files with specified header and footer', function() { var data = this.data, path = require('path'), dest = grunt.template.process(data.dest), files = grunt.file.expandFiles(this.file.src), header = grunt.file.read(grunt.template.process(data.header)), footer = grunt.file.read(grunt.template.process(data.footer)), sep = grunt.utils.linefeed; files.forEach(function(f) { var p = dest + '/' + path.basename(f), contents = grunt.file.read(f); grunt.file.write(p, header + sep + contents + sep + footer); grunt.log.writeln('File "' + p + '" created.'); }); }); grunt.registerTask('buildPages', 'Processes templates in page files', function() { // NOTE: current implementation replaces files var data = grunt.config('buildPages'), pages = data.pages, dir = data.dir, contents, curPath; pages.forEach(function(page) { curPath = dir + '/' + page.file; contents = grunt.file.read(curPath); // feed the entire pages array and current entry to the template grunt.file.write(curPath, grunt.template.process(contents, { pages: pages, curPage: page })); grunt.log.writeln('Page at "' + curPath + '" built.'); }); });};
head.tmpl:
<!DOCTYPE html><html> <head> <title><%= curPage.title %></title> </head> <body> <!-- NAV --> <ul class="nav"> <% pages.forEach(function(p) { print( '<li class="' + ((curPage === p) ? 'active' : '') + '">' + ((curPage === p) ? p.title : ('<a href="' + p.file + '">' + p.title + '</a>')) + '</li>\n' ); }); %> </ul> <!-- /NAV --> <!-- MAIN CONTENT --> <div class="main">
footer.tmpl:
</div> <!-- /MAIN CONTENT --> </body></html>