Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for NotFoundRoute, Converted CJSX utils to JSX, Started moving from CoffeeScript to ES6 #37

Merged
merged 8 commits into from
Oct 3, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/isomorphic/create-routes.coffee
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ filter = require 'lodash/collection/filter'
sortBy = require 'lodash/collection/sortBy'
last = require 'lodash/array/last'
includes = require 'underscore.string/include'
{ config } = require 'config'

module.exports = (pages, pagesReq) ->
templates = {}
@@ -12,6 +13,15 @@ module.exports = (pages, pagesReq) ->
handler: pagesReq './_template'
})

# Adds a handler for <NotFoundRoute /> that will serve as a 404 Page
if config.notFound
templates.rootNotFound = Router.createNotFoundRoute({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is a stopgap as we'll be adding support for "meta pages" soon like * routes through the coming plugin system (#36 (comment)). I'll be writing up a RFC soon with some thoughts about how the plugin system will work.

name: 'root-not-found'
path: "*"
handler: pagesReq './_404'
parentRoute: templates.root
})

# Arrange pages in data structure according to their position
# on the file system. Then use this to create routes.
#
44 changes: 44 additions & 0 deletions lib/isomorphic/gatsby-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { pages, config, relativePath } from 'config';
import filter from 'lodash/collection/filter';
import first from 'lodash/array/first';
import includes from 'underscore.string/include';

// Prefix links for Github Pages.
// TODO make this generic for all prefixing?
exports.link = function(link) {
if ((typeof __GH_PAGES__ !== "undefined" && __GH_PAGES__ !== null) && __GH_PAGES__ && (config.ghPagesURLPrefix != null)) {
return config.ghPagesURLPrefix + link;
} else {
return link;
}
};

// Get the child pages for a given template.
exports.templateChildrenPages = function(filename, state) {
// Pop off the file name to leave the relative directory
// path to this template.
var split = filename.split('/');
split.pop();
var result = split.join('/');
if (result === "") {
result = "/";
}

var childrenRoutes = first(
filter(
state.routes, function(route) { return includes(route.path, result); }
)
).childRoutes;

var childrenPaths = childrenRoutes.map(function(path) { return path.path; });

if (childrenPaths) {
var childPages = filter(pages, function(page) {
return childrenPaths.indexOf(page.path) >= 0;
});
} else {
childPages = [];
}

return childPages;
};
48 changes: 21 additions & 27 deletions lib/loaders/config-loader/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
// Generated by CoffeeScript 1.9.0
(function() {
var Router, globPages, loaderUtils, path, toml;
import toml from 'toml';
import loaderUtils from 'loader-utils';
import Router from 'react-router';
import path from 'path';

toml = require('toml');
import globPages from '../../utils/glob-pages';

loaderUtils = require('loader-utils');
module.exports = function(source) {
this.cacheable();
var callback = this.async();

Router = require('react-router');
var value = {};

path = require('path');
var directory = loaderUtils.parseQuery(this.query).directory;

globPages = require('../../utils/glob-pages');
// TODO support YAML + JSON + CSON as well here.
var config = toml.parse(source);
value.config = config;
value.relativePath = path.relative('.', directory);

module.exports = function(source) {
var callback, config, directory, value;
this.cacheable();
callback = this.async();
value = {};
directory = loaderUtils.parseQuery(this.query).directory;
config = toml.parse(source);
value.config = config;
value.relativePath = path.relative('.', directory);
return globPages(directory, (function(_this) {
return function(err, pagesData) {
value.pages = pagesData;
_this.value = [value];
return callback(null, 'module.exports = ' + JSON.stringify(value, void 0, "\t"));
};
})(this));
};

}).call(this);
// Load pages.
return globPages(directory, (err, pagesData) => {
value.pages = pagesData;
this.value = [value];
return callback(null, 'module.exports = ' + JSON.stringify(value, undefined, "\t"));
});
};
42 changes: 42 additions & 0 deletions lib/utils/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import generateStaticPages from './static-generation';
import buildProductionBundle from './build-production';
import postBuild from './post-build';
import globPages from './glob-pages';

module.exports = function(program) {
var {directory} = program;
try {
var customPostBuild = require(directory + "/post-build");
} catch (e) {
}

console.log("Generating static html pages");
return generateStaticPages(program, function(err, stats) {
if (err) {
console.log("failed at generating static html pages");
return console.log(err);
}
console.log("Compiling production bundle.js");
return buildProductionBundle(program, function(err, stats) {
if (err) {
console.log("failed to compile bundle.js");
return console.log(err);
}
console.log("Copying assets");
return postBuild(program, function(err, results) {
if (err) {
console.log("failed to copy assets");
return console.log(err);
}
if ((typeof customPostBuild !== "undefined" && customPostBuild !== null)) {
console.log("Performing custom post-build steps");
return globPages(directory, function(err, pages) {
return customPostBuild(pages, function(err) {
return console.log('Done');
});
});
}
});
});
});
};
56 changes: 56 additions & 0 deletions lib/utils/post-build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path from 'path';
import glob from 'glob';
import fs from 'fs-extra';
import async from 'async';
import parsePath from 'parse-filepath';
import _ from 'underscore';

import globPages from './glob-pages';

module.exports = function(program, cb) {
var {relativeDirectory, directory} = program;

return globPages(directory, function(err, pages) {

// Async callback to copy each file.
var copy = function(file, callback) {
// Map file to path generated for that directory.
// e.g. if file is in directory 2015-06-16-my-sweet-blog-post that got
// rewritten to my-sweet-blog-post, we find that path rewrite so
// our asset gets copied to the right directory.
var parsed = parsePath(file);
var relativePath = path.relative(directory + "/pages", file);
var oldPath = parsePath(relativePath).dirname;

// Wouldn't rewrite basePath
if (oldPath === ".") {
oldPath = "/";
var newPath = `/${parsed.basename}`;
}

if (!(oldPath === "/")) {
var page = _.find(pages, function(page) {
// Ignore files that start with underscore (they're not pages).
if (page.file.name.slice(0,1) !== '_') {
return parsePath(page.requirePath).dirname === oldPath;
}
});

newPath = parsePath(page.path).dirname + parsed.basename;
}

newPath = directory + "/public/" + newPath;
return fs.copy(file, newPath, function(err) {
return callback(err);
}
);
};

// Copy static assets to public folder.
return glob(directory + '/pages/**/?(*.jpg|*.png|*.pdf|*.gif|*.ico)', null, function(err, files) {
return async.map(files, copy, function(err, results) {
return cb(err, results);
});
});
});
};
114 changes: 114 additions & 0 deletions lib/utils/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Hapi from 'hapi';
import Boom from 'boom';
import React from 'react';
import Router from 'react-router';
import path from 'path';
import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import Negotiator from 'negotiator';
import parsePath from 'parse-filepath';
import _ from 'underscore';
import globPages from './glob-pages';
import webpackConfig from './webpack.config';

module.exports = function(program) {
var {relativeDirectory, directory} = program;

// Load pages for the site.
return globPages(directory, function(err, pages) {
try {
var HTML = require(directory + '/html');
} catch (e) {
console.log("error loading html template", e);
HTML = require(`${__dirname}/../isomorphic/html`);
}

// Generate random port for webpack to listen on.
// Perhaps should check if port is open.
var webpackPort = Math.round(Math.random() * 1000 + 1000);

var compilerConfig = webpackConfig(program, directory, 'serve', webpackPort);
var compiler = webpack(compilerConfig);

var webpackDevServer = new WebpackDevServer(compiler, {
hot: true,
quiet: true,
noInfo: true,
host: program.host,
stats: {
colors: true
}
});

// Start webpack-dev-server
webpackDevServer.listen(webpackPort, program.host, function() {});

// Setup and start Hapi to serve html + static files.
var server = new Hapi.Server();
server.connection({host: program.host, port: program.port});

server.route({
method: "GET",
path: '/bundle.js',
handler: {
proxy: {
uri: `http://localhost:${webpackPort}/bundle.js`,
passThrough: true,
xforward: true
}
}
});

server.route({
method: "GET",
path: '/html/{path*}',
handler(request, reply) {
if (request.path === "favicon.ico") {
return reply(Boom.notFound());
}

var html = React.renderToStaticMarkup(React.createElement(HTML));
html = "<!DOCTYPE html>\n" + html;
return reply(html);
}
});

server.route({
method: "GET",
path: '/{path*}',
handler: {
directory: {
path: directory + "/pages",
listing: false,
index: false
}
}
});

server.ext('onRequest', function(request, reply) {
var negotiator = new Negotiator(request.raw.req);

if (negotiator.mediaType() === "text/html") {
request.setUrl("/html" + request.path);
return reply.continue();
} else {
// Rewrite path to match disk path.
var parsed = parsePath(request.path);
var page = _.find(pages, function(page) { return page.path === (parsed.dirname + "/"); });

if (page) {
request.setUrl(`/${parsePath(page.requirePath).dirname}/${parsed.basename}`);
}

return reply.continue();
}
});

return server.start(function(err) {
if (err) {
console.log(err);
}
return console.log("Listening at:", server.info.uri);
});
});
};
52 changes: 0 additions & 52 deletions lib/utils/static-entry.cjsx

This file was deleted.

67 changes: 67 additions & 0 deletions lib/utils/static-entry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import Router from 'react-router';
import find from 'lodash/collection/find';
import filter from 'lodash/collection/filter';
import createRoutes from 'create-routes';
import HTML from 'html';
import app from 'app';
import values from 'config';

var pages = values.pages, config = values.config;
var routes = {};

app.loadContext(function(pagesReq) {
routes = createRoutes(pages, pagesReq);

// Remove templates files.
return pages = filter(pages, function(page) {
return page.path != null;
});
});

module.exports = function(locals, callback) {
return Router.run([routes], locals.path, function(Handler, state) {
var body, childPages, childrenPaths, html, page;
page = find(pages, function(page) {
return page.path === state.pathname;
});

// Pull out direct children of the template for this path.
childrenPaths = state.routes[state.routes.length - 2].childRoutes.map(function(route) {
return route.path;
});
if (childrenPaths) {
childPages = filter(pages, function(page) {
return childrenPaths.indexOf(page.path) >= 0;
});
} else {
childPages = [];
}

body = "";
html = "";
try {
body = React.renderToString(
<Handler
config={config}
pages={pages}
page={page}
childPages={childPages}
state={state}
/>
);
html = "<!DOCTYPE html>\n" + React.renderToStaticMarkup(
<HTML
config={config}
page={page}
body={body}
/>
);
}
catch (e) {
console.error(e.stack);
}

return callback(null, html);
});
};
44 changes: 0 additions & 44 deletions lib/utils/web-entry.cjsx

This file was deleted.

58 changes: 58 additions & 0 deletions lib/utils/web-entry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import Router from 'react-router';
import find from 'lodash/collection/find';
import filter from 'lodash/collection/filter';
import createRoutes from 'create-routes';
import app from 'app';
import first from 'lodash/array/first';

function loadConfig(cb) {
var stuff = require('config');
if (module.hot) {
module.hot.accept(stuff.id, function() {
return cb();
});
}
return cb();
};

loadConfig(function() {
return app.loadContext(function(pagesReq) {
var config, pages, ref, relativePath, router, routes;
ref = require('config'),
pages = ref.pages,
config = ref.config,
relativePath = ref.relativePath;

routes = createRoutes(pages, pagesReq);
// Remove templates files.
pages = filter(pages, function(page) {
return page.path != null;
});

// Route already exists meaning we're hot-reloading.
if (router) {
return router.replaceRoutes([app]);
} else {
return router = Router.run([routes], Router.HistoryLocation, function(Handler, state) {
var page;
page = find(pages, function(page) {
return page.path === state.pathname;
});

// Let app know the route is changing.
if (app.onRouteChange) {
app.onRouteChange(state, page, pages, config);
}
return React.render(
<Handler
config={config}
pages={pages}
page={page}
state={state}
/>, typeof window !== "undefined" ? document.getElementById("react-mount") : void 0);
});
}

});
});
191 changes: 191 additions & 0 deletions lib/utils/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import webpack from 'webpack';
import StaticSiteGeneratorPlugin from 'static-site-generator-webpack-plugin';

module.exports = function(program, directory, stage, webpackPort = 1500, routes=[]) {
var output = function() {
switch (stage) {
case "serve":
return {path: directory,
filename: 'bundle.js',
publicPath: `http://${program.host}:${webpackPort}/`};
case "production":
return {filename: "bundle.js",
path: directory + "/public"};
case "static":
return {path: directory + "/public",
filename: "bundle.js",
libraryTarget: 'umd'};
}
};

var entry = function() {
switch (stage) {
case "serve":
return [
`${__dirname}/../../node_modules/webpack-dev-server/client?${program.host}:${webpackPort}`,
`${__dirname}/../../node_modules/webpack/hot/only-dev-server`,
`${__dirname}/web-entry`
];
case "production":
return [
`${__dirname}/web-entry`
];
case "static":
return [
`${__dirname}/static-entry`
];
}
};

var plugins = function() {
switch (stage) {
case "serve":
return [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
__GH_PAGES__: JSON.stringify(JSON.parse(process.env.GATSBY_ENV === "gh-pages"))
})
];
case "production":
return [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
},
__GH_PAGES__: JSON.stringify(JSON.parse(process.env.GATSBY_ENV === "gh-pages"))
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin()
];
case "static":
return [
new StaticSiteGeneratorPlugin('bundle.js', routes),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
},
__GH_PAGES__: JSON.stringify(JSON.parse(process.env.GATSBY_ENV === "gh-pages"))
})
];
}
};

var resolve = function() {
return {
extensions: ['', '.js', '.jsx', '.cjsx', '.coffee', '.json', '.less', '.toml', '.yaml'],
modulesDirectories: [directory, `${__dirname}/../isomorphic`, `${directory}/node_modules`, "node_modules"]
};
};

var module = function() {
switch (stage) {
case "serve":
return {loaders: [
{ test: /\.css$/, loaders: ['style', 'css']},
{ test: /\.cjsx$/, loaders: ['react-hot', 'coffee', 'cjsx']},
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['react-hot', 'babel']
},
{
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['react-hot', 'babel']
},
{ test: /\.less/, loaders: ['style', 'css', 'less']},
{ test: /\.coffee$/, loader: 'coffee' },
{ test: /\.md$/, loader: 'markdown' },
{ test: /\.html$/, loader: 'raw' },
{ test: /\.json$/, loaders: ['json'] },
{ test: /^((?!config).)*\.toml$/, loaders: ['toml'] }, // Match everything except config.toml
{ test: /\.png$/, loader: 'null' },
{ test: /\.jpg$/, loader: 'null' },
{ test: /\.svg$/, loader: 'null' },
{ test: /\.ico$/, loader: 'null' },
{ test: /\.pdf$/, loader: 'null' },
{ test: /\.txt$/, loader: 'null' },
{ test: /config\.[toml|yaml|json]/, loader: 'config', query: {
directory: directory
} }
]};
case "static":
return {loaders: [
{ test: /\.css$/, loaders: ['css']},
{ test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['babel']
},
{
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['babel']
},
{ test: /\.less/, loaders: ['css', 'less']},
{ test: /\.coffee$/, loader: 'coffee' },
{ test: /\.md$/, loader: 'markdown' },
{ test: /\.html$/, loader: 'raw' },
{ test: /\.json$/, loaders: ['json'] },
{ test: /^((?!config).)*\.toml$/, loaders: ['toml'] }, // Match everything except config.toml
{ test: /\.png$/, loader: 'null' },
{ test: /\.jpg$/, loader: 'null' },
{ test: /\.svg$/, loader: 'null' },
{ test: /\.ico$/, loader: 'null' },
{ test: /\.pdf$/, loader: 'null' },
{ test: /\.txt$/, loader: 'null' },
{ test: /config\.[toml|yaml|json]/, loader: 'config', query: {
directory: directory
} }
]};
case "production":
return {loaders: [
{ test: /\.css$/, loaders: ['style', 'css']},
{ test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['babel']
},
{
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
loaders: ['babel']
},
{ test: /\.less/, loaders: ['style', 'css', 'less']},
{ test: /\.coffee$/, loader: 'coffee' },
{ test: /\.md$/, loader: 'markdown' },
{ test: /\.html$/, loader: 'raw' },
{ test: /\.json$/, loaders: ['json'] },
{ test: /^((?!config).)*\.toml$/, loaders: ['toml'] }, // Match everything except config.toml
{ test: /\.png$/, loader: 'null' },
{ test: /\.jpg$/, loader: 'null' },
{ test: /\.svg$/, loader: 'null' },
{ test: /\.ico$/, loader: 'null' },
{ test: /\.pdf$/, loader: 'null' },
{ test: /\.txt$/, loader: 'null' },
{ test: /config\.[toml|yaml|json]/, loader: 'config', query: {
directory: directory
} }
]};
}
};
return {
context: directory + "/pages",
node: {
__filename: true
},
entry: entry(),
debug: true,
devtool: 'eval',
output: output(),
resolveLoader: {
modulesDirectories: [`${__dirname}/../../node_modules`, `${__dirname}/../loaders`]
},
plugins: plugins(),
resolve: resolve(),
module: module()
};
};