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

Reboot with TDD to get this up to speed #13

Merged
merged 23 commits into from Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e2a50a8
Simplify pages' configuration format
FND Nov 7, 2018
264cf4b
Simplify internals by avoiding excessive asset-manager references
FND Nov 7, 2018
328b26b
Add support for JavaScript modules as pages' configuration format
FND Nov 7, 2018
d069aea
Create abstraction for page hierarchies
FND Nov 8, 2018
ac689aa
Create abstraction for sites (i.e. styleguide instances)
FND Nov 9, 2018
b5143e3
Generate site structure
FND Nov 10, 2018
d051bb9
Replace obsolete implementation
FND Nov 12, 2018
508fe5c
Restoring functionality
moonglum Nov 14, 2018
2e95dba
Delete generated files in node
moonglum Nov 14, 2018
268326f
Move acorn to dev dependencies
moonglum Nov 14, 2018
dcafccf
Renamed examples to snippets
moonglum Nov 14, 2018
a5f6992
Adjust package*json to .editorconfig
moonglum Nov 14, 2018
29bf668
Rename file
moonglum Nov 14, 2018
20c6e72
Report writing files
moonglum Nov 14, 2018
6db1f5e
Provide the correct path to style.css
moonglum Nov 14, 2018
7a189f8
Switch to released version of faucet-pipeline-sass
moonglum Nov 15, 2018
7cc4466
Preparation: Users need to require their pages themselves
moonglum Nov 15, 2018
c10ccbd
Another library that doesn't bother to put a newline at the end of a …
moonglum Nov 15, 2018
c7bebc8
Cleanup
moonglum Nov 15, 2018
e23054c
Cleanup faucet.config.js
moonglum Nov 15, 2018
d323188
Add tests for PageRenderer
moonglum Nov 15, 2018
eab05ef
Preparatory Refactoring: Load all pages before starting the rendering
moonglum Nov 16, 2018
270fadf
Generate the navigation
moonglum Nov 16, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,3 +1,4 @@
/node_modules
/dist
/.eslintcache

/dist
8 changes: 6 additions & 2 deletions faucet.config.js
Expand Up @@ -3,11 +3,15 @@ let path = require("path");

module.exports = {
styleguide: [{
source: "./pages.json",
source: "./pages.js",
moonglum marked this conversation as resolved.
Show resolved Hide resolved
target: "./dist",
port: 8080,
title: "Example Styleguide",
description: "This is an example styleguide"
description: "This is an example styleguide",
renderers: {
html: require("./lib/html"),
jsx: require("./lib/complate")
moonglum marked this conversation as resolved.
Show resolved Hide resolved
}
}],
sass: [{
source: "./lib/style.scss",
Expand Down
4 changes: 1 addition & 3 deletions lib/complate.js
Expand Up @@ -11,9 +11,7 @@ let bundle = new VirtualBundle("./", {
browsers: {}
});
moonglum marked this conversation as resolved.
Show resolved Hide resolved

exports.language = "jsx";

exports.render = function(snippet) {
module.exports = function(snippet) {
return new Promise((resolve, reject) => {
// TODO: FND has something more sophisticated somewhere
let [ imports, macro ] = snippet.split("\n\n", 2);
Expand Down
40 changes: 34 additions & 6 deletions lib/generate_layout.js
@@ -1,7 +1,5 @@
let generateNavigation = require("./generate_navigation");

module.exports = pages => {
let navigation = generateNavigation(pages);
module.exports = tree => {
let navigation = generateNavigation(tree);

return (page, content) => `<!doctype html>
<html lang="${page.language}">
Expand All @@ -11,14 +9,44 @@ module.exports = pages => {
<meta name="description" content="${page.description}">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="/style.css" rel="stylesheet">
moonglum marked this conversation as resolved.
Show resolved Hide resolved
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/themes/prism.css" integrity="sha256-/Kfdz9pXGPe+bFF+TtxHqbg6F9c3Rb0jN48uy+2b/do=" crossorigin="anonymous" />
</head>
<body>
<nav>${navigation}</nav>
<main>
<h1>${page.heading}</h1>
<h1>${page.title}</h1>
${content}
</main>
</body>
</html>`;
};

// TODO: Implement a real generateNavigation function
moonglum marked this conversation as resolved.
Show resolved Hide resolved
function generateNavigation(tree) {
return `<ul>
<li><a href="/">Home</a></li>
<li>
<a href="/atoms">Atoms</a>
<ul>
<li><a href="/atoms/button">Button</a></li>
<li><a href="/atoms/strong">Strong</a></li>
</ul>
</li>
</ul>`;
}

// function generateNavigation(pages) {
// if(pages.length === 0) {
// return "";
// }

// let lis = pages.map(page =>
// `<li>${generateTitle(page)}${generateNavigation(page.children)}</li>`).
// join("\n");

// return `<ul>${lis}</ul>`;
// }

// function generateTitle(page) {
// return page.file ? `<a href="/${page.slug}">${page.heading}</a>` :
// `${page.heading}`;
// }
18 changes: 0 additions & 18 deletions lib/generate_navigation.js

This file was deleted.

40 changes: 0 additions & 40 deletions lib/generate_renderer.js

This file was deleted.

9 changes: 9 additions & 0 deletions lib/highlight_snippet.js
@@ -0,0 +1,9 @@
let Prism = require("prismjs");
let loadLanguages = require("prismjs/components/");

module.exports = function(snippet, language) {
if(!Prism.languages[language]) {
loadLanguages([language]);
}
return Prism.highlight(snippet, Prism.languages[language], language);
};
4 changes: 1 addition & 3 deletions lib/html.js
@@ -1,3 +1 @@
exports.language = "html";

exports.render = template => Promise.resolve(template);
module.exports = template => Promise.resolve(template);
45 changes: 12 additions & 33 deletions lib/html_renderer.js
@@ -1,51 +1,30 @@
let { HtmlRenderer } = require("commonmark");
let Prism = require("prismjs");
let loadLanguages = require("prismjs/components/");
let preloadedLanguages = ["html", "css", "javascript"];
let highlightSnippet = require("./highlight_snippet");

class ImprovedHtmlRenderer extends HtmlRenderer {
constructor(...args) {
super(...args);
render(...args) {
this.examples = [];
moonglum marked this conversation as resolved.
Show resolved Hide resolved
this.renderers = {};
this.options.renderers.forEach(({ language, render }) => {
this.renderers[language] = render;
if(!preloadedLanguages.includes(language)) {
loadLanguages([language]);
}
});
let html = super.render(...args);
return { html, examples: this.examples };
}

// overwriting an existing function that does not adhere to JS naming conventions
// eslint-disable-next-line camelcase
code_block(node) {
let highlightedTemplate = this.highlightTemplate(node);
let exampleURI = this.renderTemplate(node);

this.lit(`<component-preview>
<iframe src="${exampleURI}" class="rendered"></iframe>
<pre>${highlightedTemplate}</pre>
<iframe src="${this.renderedBlockURI(node)}" class="rendered"></iframe>
<pre>${this.highlightedBlock(node)}</pre>
</component-preview>`);
}

highlightTemplate({ info, literal }) {
let language = Prism.languages[info];
if(!language) {
throw new Error(`Styleguide ran accross unknown language ${info} (highlighter)`);
}
return Prism.highlight(literal, language, info);
renderedBlockURI({ info, literal }) {
let slug = `${this.examples.length}.html`;
this.examples.push({ language: info, snippet: literal, slug });
return slug;
}

renderTemplate({ info, literal }) {
let url = `/${this.options.page.slug}/${this.examples.length}.html`;
let renderer = this.renderers[info];
if(!renderer) {
throw new Error(`Styleguide ran accross unknown language ${info} (renderer)`);
}

this.examples.push(() => renderer(literal).then(rendered => ({ rendered, url })));

return url;
highlightedBlock({ info, literal }) {
return highlightSnippet(literal, info);
}
}

Expand Down
84 changes: 6 additions & 78 deletions lib/index.js
@@ -1,81 +1,9 @@
let fs = require("fs");
let { abort, promisify } = require("faucet-pipeline-core/lib/util");
let readFile = promisify(fs.readFile);
let generateLayout = require("./generate_layout");
let generateRenderer = require("./generate_renderer");
let Page = require("./page");

module.exports = (config, assetManager, opts) => {
let runners = config.map(runnerConfig =>
makeRunner(runnerConfig, assetManager));

return filepaths => Promise.all(runners.map(runner => runner(filepaths)));
};

function makeRunner(config, assetManager) {
if(!config.source || !config.target) {
abort("ERROR: Styleguide configuration requires both target and source");
}

let source = assetManager.resolvePath(config.source);
let target = assetManager.resolvePath(config.target, { enforceRelative: true });
config.description = config.description || "Styleguide";
config.language = config.language || "en";

if(config.port) {
let server = require("./server");
server(target, config.port);
}

// renderers should be plugins, and which ones can loaded should be cofigured
let renderers = [
require("./html"),
require("./complate")
];
let renderPage = generateRenderer(renderers, target, assetManager);

let shouldRenderPage = (file, changedFiles) => {
if(!changedFiles) {
return true;
}

return changedFiles.includes(assetManager.resolvePath(file));
};
let Site = require("./site");

module.exports = (config, assetManager, options) => {
let sites = config.map(siteConfig => new Site(siteConfig, assetManager));
return filepaths => {
if(filepaths && filepaths.includes(source)) {
// if the `target` file changes, rebuild all
filepaths = undefined;
}

return readFile(source).
then(raw => {
let pages = JSON.parse(raw).
map(rawPage => new Page(rawPage, config, assetManager.resolvePath));

return Promise.all(pages.map(page => {
return page.readHeaders().
catch(_ => {
console.error(`Could not read headers in ${page.file}`);
});
})).
then(() => pages);
}).then(pages => {
let layout = generateLayout(pages);

return Promise.all(
pages.
reduce((accumulator, page) => {
return accumulator.concat(page, page.children);
}, []).
map(page => {
if(!shouldRenderPage(page.file, filepaths)) {
return Promise.resolve(null);
}

return renderPage(page, layout);
})
);
});
let builds = sites.map(site => site.generate(filepaths));
return Promise.all(builds);
};
}
};
48 changes: 26 additions & 22 deletions lib/page.js
@@ -1,30 +1,34 @@
let { repr } = require("faucet-pipeline-core/lib/util");
let colonParse = require("metacolon");

class Page {
constructor({ slug, file, children }, config, resolvePath) {
this.slug = slug;
this.file = resolvePath(file);
this.children = (children || []).map(child => {
child.slug = `${slug}/${child.slug}`;
return new Page(child, config, resolvePath);
});
this.config = config;
module.exports = class Page {
constructor(slug, sourcePath, children) {
this.slug = slug || "";
this.sourcePath = sourcePath;
this.children = children || null;
}

readHeaders() {
return colonParse(this.file).
then(({ headers }) => {
this.description = headers.description || this.config.description;
this.language = headers.language || this.config.language;
this.heading = headers.title;
this.title = [headers.title, this.config.title].join(" | ");
return Promise.all(this.children.map(child => child.readHeaders()));
});
getChild(slug) {
return this.children.get(slug);
}

body() {
return colonParse(this.file).then(({ body }) => body);
async load() {
if(this.body) { // heuristic
return Promise.resolve(this);
}

let { headers, body } = await colonParse(this.sourcePath);
// XXX: secondary fallbacks YAGNI?
moonglum marked this conversation as resolved.
Show resolved Hide resolved
this.language = headers.lang || headers.language || null;
this.title = headers.title || null;
this.description = headers.desc || headers.description || null;
this.body = body;
return this;
}
}

module.exports = Page;
toString() {
let filepath = repr(this.sourcePath, false);
let suffix = this.body ? " resolved" : "";
return `<Page ${repr(this.slug, false)} ${filepath}${suffix}>`;
}
};