Skip to content

Commit

Permalink
Creates sitemap.xml from routes #33
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoltan Olah committed Mar 6, 2015
1 parent acccc76 commit 034daa5
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 81 deletions.
109 changes: 109 additions & 0 deletions app/lib/StaticTools.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict';

var Sitemap = require('sitemap');
var path = require('path');
var fs = require('fs');
var mkdirp = require('mkdirp');

var StaticTools = {
// Generates a sitemap.xml from your pages. Assigns priority based on depth.
// You will probably need to customize this for your own app if you want per
// page beahviour
makeSitemap: function(pages, hostname) {
var urls = [];
var now = new Date();

pages.forEach(function(page) {
// / => 0, /foo => 1, /foo/bar => 2
var depth = (page.match(/\//g) || []).length;

// / => 1, /foo => 0.5, /foo/bar => 0.33
var priority = ( page === '/' ? 1 : 1 / (depth + 1) );

urls.push({
url: page, changefreq: 'weekly', priority: priority, lastmod: now
});
}.bind(this));

var sitemap = Sitemap.createSitemap ({
hostname: hostname,
cacheTime: 600000, // 600 sec cache period
urls: urls
});

return sitemap.toString();
},

// Returns an array containing paths that correspond to routes
// For example: ['/', '/about']
gather: function(routes, parentPath) {
var result = [];
routes = Array.isArray(routes) ? routes : [routes];

routes.forEach(function(route) {
var props = route._store.props;
var routePath = props.path;

if (routePath) {
routePath = parentPath ? path.join(parentPath, routePath) : routePath;

result.push(routePath);
}
if (props.children) {
result = result.concat(this.gather(props.children, routePath));
}
}.bind(this));

return result;
},

// Takes an array of paths, a target path, path component key and an array of
// values. Returns a new array of paths with the values interpolated in place
// of the path component key.
//
// E.g:
// interpolate(['/', '/what/:name'], '/what/:name', 'name', ['foo', 'bar'])
//
// returns ['/', '/what/foo', '/what/bar']
interpolate: function(paths, path, key, values) {
var result = [];

paths.forEach(function(routePath) {
if (routePath === path) {
values.forEach(function(value) {
var interpolated = routePath.replace(':' + key, value);

result.push(interpolated);
});
} else {
result.push(routePath);
}
});

return result;
},

// Converts a page to a filename, e.g '/' => 'index.html'
pageToFilename: function(page, extension) {
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

if (endsWith(page, '/'))
page += 'index';

return page + '.' + extension;
},

// converts a page into a filepath relative to cwd and writes contents there
writePage: function(dir, page, contents, extension) {
var filePath = path.join(dir, this.pageToFilename(page, extension));

// make all of the directories in filePath, like mkdir -p
mkdirp.sync(path.dirname(filePath));

fs.writeFileSync(filePath, contents);
}
}

module.exports = StaticTools;
95 changes: 15 additions & 80 deletions app/static.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// script assumes process.cwd() is the output directory
var outputDir = process.cwd();

require('node-jsx').install({ extension: '.jsx' });

Expand All @@ -7,11 +8,7 @@ var React = require('react');
var HtmlComponent = React.createFactory(require('./components/Html'));
var Router = require('react-router');
var HeadParams = require('./lib/HeadParams');

var cwd = process.cwd();
var path = require('path');
var fs = require('fs');
var mkdirp = require('mkdirp');
var StaticTools = require('./lib/StaticTools');

var routes = require('./components/Routes');
var Collections = require('./components/Collections');
Expand All @@ -22,22 +19,26 @@ var Collections = require('./components/Collections');
var productNames = _.pluck(Collections.Products, 'name');
var jobNames = _.pluck(Collections.Jobs, 'name');

var paths = gather(routes);
paths = interpolate(paths, '/what/:name', 'name', productNames);
paths = interpolate(paths, '/careers/:name', 'name', jobNames);
var pages = StaticTools.gather(routes);
pages = StaticTools.interpolate(pages, '/what/:name', 'name', productNames);
pages = StaticTools.interpolate(pages, '/careers/:name', 'name', jobNames);

// Write the sitemap with the pages we have so far
StaticTools.writePage(outputDir, '/sitemap',
StaticTools.makeSitemap(pages, 'http://percolatestudio.com'), 'xml');

paths.push('/error'); // Will hit the NotFound route and generate error.html
pages.push('/error'); // Will hit the NotFound route and generate error.html

// Manually specify indexes on these directory paths. Otherwise navigating to
// foo/ will do a directory listing. Its possible we can automate this in
// interrogate()
paths.push('/careers/');
paths.push('/what/');
pages.push('/careers/');
pages.push('/what/');

var headParams = new HeadParams();

// Render each path
paths.forEach(function(page) {
pages.forEach(function(page) {
Router.run(routes, page, function (Handler, state) {
console.log(page);
var bodyElement = React.createFactory(Handler)({
Expand All @@ -51,72 +52,6 @@ paths.forEach(function(page) {
markup: React.renderToString(bodyElement)
}));

writePage(page, html);
});
});

// converts a page into a filepath relative to cwd and writes contents there
function writePage(page, contents) {
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

if (endsWith(page, '/'))
page += 'index'

var filePath = path.join(cwd, page + '.html');

// make all of the directories in filePath, like mkdir -p
mkdirp.sync(path.dirname(filePath));

fs.writeFileSync(filePath, contents);
}

// Returns an array containing paths that correspond to routes
// For example: ['/', '/about']
function gather(routes, parentPath) {
var result = [];
routes = Array.isArray(routes) ? routes : [routes];

routes.forEach(function(route) {
var props = route._store.props;
var routePath = props.path;

if (routePath) {
routePath = parentPath ? path.join(parentPath, routePath) : routePath;

result.push(routePath);
}
if (props.children) {
result = result.concat(gather(props.children, routePath));
}
});

return result;
};

// Takes an array of paths, a target path, path component key and an array of
// values. Returns a new array of paths with the values interpolated in place
// of the path component key.
//
// E.g:
// interpolate(['/', '/what/:name'], '/what/:name', 'name', ['foo', 'bar'])
//
// returns ['/', '/what/foo', '/what/bar']
function interpolate(paths, path, key, values) {
var result = [];

paths.forEach(function(routePath) {
if (routePath === path) {
values.forEach(function(value) {
var interpolated = routePath.replace(':' + key, value);

result.push(interpolated);
});
} else {
result.push(routePath);
}
StaticTools.writePage(outputDir, page, html, 'html');
});

return result;
}
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
"dependencies": {
"autosize": "jackmoore/autosize#d8bf2a3f40f5d188a899c87a7e982d2d4de48e70",
"express": "^4.3.2",
"fastclick": "^1.0.6",
"jquery": "^2.1.3",
"lodash": "^3.3.1",
"node-jsx": "^0.12.0",
"picturefill": "^2.2.1",
"react": "^0.12.0",
"react-router": "^0.12.4",
"fastclick": "^1.0.6"
"sitemap": "^0.7.4"
},
"devDependencies": {
"css-loader": "^0.9.1",
Expand Down

0 comments on commit 034daa5

Please sign in to comment.