Skip to content

Commit 7c2d4c8

Browse files
add Recent Components page
1 parent 4059e78 commit 7c2d4c8

11 files changed

+407
-345
lines changed

build.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ const startTime = process.hrtime();
1515
// require('./src/home-build')()
1616
// console.log('home build complete')
1717

18+
const componentsBuildList = require('./src/components-build-list');
1819
const componentsBuildIndex = require('./src/components-build-index');
1920
const componentsBuildPages = require('./src/components-build-pages');
2021
const componentsBuildScreenshots = require('./src/components-build-screenshots');
2122

2223
// See src/components-build-defaults for list of options that can be overriden
2324
const options = {
2425
components: {
25-
globPattern: 'src/components/article-lists/*.html',
26+
globPattern: 'src/components/b**/*.html',
2627
// frontMatter: {
2728
// bodyClass: 'bg-red',
2829
// screenshot: {
@@ -35,20 +36,21 @@ const options = {
3536
// },
3637
};
3738

38-
// Note that componentsBuildIndex() generates the index *and* the JSON
39-
// file listing all the components pages and screenshots should be built for.
40-
// Scenario: if you are working on one component, uncomment and set componentsGlobPattern
41-
// in the options above to only (re-)generate the index, pages, and screenshots for the
42-
// corresponding category. When you are done, comment componentsGlobPattern back, comment
39+
// Note that componentsBuildList() generates a temporary components list (saved as JSON),
40+
// which is re-used by later steps (for performance).
41+
// If you are working on one component, uncomment and set components.GlobPattern
42+
// in the options above to only (re-)generate the list, index, pages, and screenshots for the
43+
// corresponding category. Once you are done, comment components.GlobPattern back, comment
4344
// the line below that generate the screenshots, then run the script -- this will
44-
// re-create the full index page for all components, re-generate all the pages (since they need
45+
// re-create the full index pages for all components, re-generate all the pages (since they need
4546
// to cross-reference the new component), and use the previously generated screenshots as well
4647
// as the new one (i.e. no need to re-generate *all* the screenshots unless you made
4748
// modifications to the screenshots script itself).
4849
co(function* generator() {
49-
yield componentsBuildIndex(options); // <- builds component index page and JSON index
50-
yield componentsBuildPages(options); // <- comment to skip building pages
51-
// yield componentsBuildScreenshots(options); // <- comment to skip building screenshots
50+
yield componentsBuildList(options); // <- builds temporary components list (JSON)
51+
yield componentsBuildIndex(options); // <- builds index pages (by category & most recent)
52+
yield componentsBuildPages(options); // <- comment to skip building pages
53+
yield componentsBuildScreenshots(options); // <- comment to skip building screenshots
5254
}).then(() => {
5355
const elapsed = process.hrtime(startTime);
5456
console.log(chalk.green('All done'), chalk.dim(prettyHrtime(elapsed)));

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
"lazysizes": "^3.0.0-rc2",
119119
"lodash": "^4.16.6",
120120
"mkdirp": "^0.5.1",
121+
"moment": "^2.17.1",
121122
"nightmare": "^2.8.1",
122123
"node-minify": "^1.3.9",
123124
"nodegit": "^0.16.0",

src/components-build-defaults.js

+25-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const moment = require('moment');
12
const titleize = require('titleize');
23

34
const cleanTitleize = str => titleize(str.replace(/(_|-)/g, ' '));
@@ -6,23 +7,41 @@ module.exports = {
67
// Components
78
components: {
89
globPattern: 'src/components/**/*.html', // source components to process
9-
indexByCategory: {
10-
path: 'components/index.html', // target location of index by category
11-
json: 'tmp/componentsByCategory.json', // temporary JSON file built by the index
12-
},
10+
tempListPath: 'tmp/components.json', // temporary JSON file built by the index
1311
build: {
1412
pages: true, // false to skip building pages
1513
screenshots: true, // false to skip building screenshots
1614
},
15+
index: { // list index pages to build
16+
byCategory: { // components by category
17+
title: 'Components',
18+
path: 'components/index.html', // target location of index by category
19+
sortAllBy: [['src'], ['asc']], // sort by file location will do
20+
limitAll: false, // use all components
21+
createSectionsBy: 'category', // create a section for each category
22+
showSectionsTOC: true, // show Table of Contents (e.g. categories)
23+
},
24+
recent: { // recent components
25+
title: 'Recent Components',
26+
path: 'components/recent.html', // target location of recent index
27+
sortAllBy: [['creationTime'], ['desc']], // sort by most recent component first
28+
limitAll: 50, // use the 50 most recent ones
29+
createSectionsBy: c => moment(c.creationTime).format('YYYY-MM-DD'), // group by day
30+
prettifySection: v => moment(v).format('LL'), // display as day
31+
showSectionsTOC: false, // no need for Table of Contents
32+
},
33+
},
34+
page: { // options related to each component page
35+
composeTitle: (category, name) => `${category} | ${name}`, // compose title from cat, name
36+
},
1737
prettify: {
1838
id: cleanTitleize, // prettify component id into component name
1939
category: cleanTitleize, // prettify component category
20-
title: (category, name) => `${category} | ${name}`, // compose title from category and name
40+
creationTime: v => moment(v).format('LLL'), // prettify component creation time
2141
},
2242
frontMatter: { // font matter defaults (i.e. optional)
2343
name: undefined, // undefined to infer component name
2444
title: undefined, // undefined to infer component title
25-
category: undefined, // undefined to infer component category
2645
bodyClass: 'bg-white', // class to apply on <body>
2746
screenshot: { // per-component screenshot options
2847
selector: '[data-name="component"]', // DOM element to capture

src/components-build-index.js

+31-157
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,49 @@
11
const _ = require('lodash');
22
const chalk = require('chalk');
3-
const co = require('co');
4-
const crypto = require('crypto');
5-
const fm = require('json-front-matter');
63
const fs = require('fs');
7-
const glob = require('glob');
84
const mkdirp = require('mkdirp');
9-
const nodegit = require('nodegit');
105
const path = require('path');
116
const prettyHrtime = require('pretty-hrtime');
12-
const rmHtmlExt = require('remove-html-extension');
137

148
const defaults = require('./components-build-defaults');
15-
16-
/*
17-
* Return the first commit that even introduced a component, given its path
18-
*/
19-
const getComponentFirstCommit = (repo, componentPath) =>
20-
co(function* generator() {
21-
const headCommit = yield repo.getHeadCommit();
22-
const walker = repo.createRevWalk();
23-
walker.push(headCommit.id());
24-
walker.sorting(nodegit.Revwalk.SORT.TIME);
25-
// Not happy about that hard-coded constant here, but this is the only way to
26-
// tell the walker to go as far back in time as possible. No amount of asking
27-
// on nodegit's Slack or Gitter gave me a better way to do it.
28-
const historyCommits = yield walker.fileHistoryWalk(componentPath, 30000);
29-
return historyCommits[historyCommits.length - 1].commit;
30-
});
31-
32-
/*
33-
* Return the list of components
34-
*/
35-
const getComponents = options => new Promise((resolve, reject) => {
36-
glob(options.components.globPattern, {}, (err, componentPaths) => {
37-
if (err) {
38-
reject(err);
39-
return;
40-
}
41-
const npmPackage = JSON.parse(fs.readFileSync('package.json', 'utf8'));
42-
co(function* generator() {
43-
const repo = yield nodegit.Repository.open(path.resolve('.'));
44-
45-
const components = [];
46-
for (let comp_idx = 0; comp_idx < componentPaths.length; comp_idx += 1) {
47-
const componentPath = componentPaths[comp_idx];
48-
49-
const firstCommit = yield getComponentFirstCommit(repo, componentPath);
50-
const creationTime = firstCommit.date().getTime();
51-
52-
const componentHtml = fs.readFileSync(componentPath, 'utf8');
53-
const fmParsed = fm.parse(componentHtml);
54-
const srcFrontMatter = fmParsed.attributes || {};
55-
const frontMatter = _.merge({}, options.components.frontMatter, srcFrontMatter);
56-
57-
const targetDir = componentPath.replace('src/', '').replace('.html', '');
58-
59-
const tokens = rmHtmlExt(componentPath).replace('src/components/', '').split('/');
60-
const category = tokens[0];
61-
const id = tokens[1];
62-
63-
// Compute component signature based on the Tachyons version and the contents of the
64-
// component itself. This can be used to bust the browser cache of screenshots.
65-
const md5sum = crypto.createHash('md5');
66-
md5sum.update(npmPackage.version);
67-
md5sum.update(componentHtml);
68-
const signature = md5sum.digest('hex');
69-
70-
const page = {
71-
path: `${targetDir}/index.html`,
72-
href: `/${targetDir}/index.html`,
73-
name: frontMatter.name || options.components.prettify.id(id),
74-
category: frontMatter.category || options.components.prettify.category(category),
75-
};
76-
page.title = frontMatter.title ||
77-
options.components.prettify.title(page.category, page.name);
78-
79-
const screenshot = {
80-
path: `${targetDir}/${options.screenshot.basename}`,
81-
href: `/${targetDir}/${options.screenshot.basename}?version=${signature}`,
82-
};
83-
84-
components.push({
85-
id,
86-
category,
87-
src: componentPath,
88-
creationTime,
89-
signature,
90-
page,
91-
screenshot,
92-
// This is the raw front matter, as found in the component source, NOT merged
93-
// with any defaults, so that it is easier to spot the overrides.
94-
// It is up to build scripts to merge with options.components.frontMatter down the road.
95-
frontMatter: srcFrontMatter,
96-
});
97-
}
98-
resolve(components);
99-
}); // generator
100-
}); // glob
101-
});
9+
const createSections = require('./components-build-sections');
10210

10311
module.exports = _options => new Promise((resolve, reject) => {
10412
const options = _.merge({}, defaults, _options);
10513
const startTime = process.hrtime();
10614
console.log(chalk.magenta('Working on components index...'));
107-
co(function* generator() {
108-
const components = yield getComponents(options);
109-
110-
// Map by category
111-
const componentsByCategory = {};
112-
components.forEach((component) => {
113-
const categoryKey = component.page.category;
114-
componentsByCategory[categoryKey] = componentsByCategory[categoryKey] || [];
115-
componentsByCategory[categoryKey].push(component);
116-
});
117-
118-
// Map by time
119-
// componentsByTime[component.creationTime] = componentsByTime[component.creationTime] || [];
120-
// componentsByTime[component.creationTime].push(component);
121-
122-
// So what did we find?
123-
const categories = Object.keys(componentsByCategory);
124-
console.log(
125-
'- Found', components.length, components.length > 1 ? 'components' : 'component',
126-
'in', categories.length, categories.length > 1 ? 'categories' : 'category'
127-
);
128-
129-
// Save by category (for later re-use)
130-
mkdirp.sync(path.dirname(options.components.indexByCategory.json));
131-
fs.writeFileSync(
132-
options.components.indexByCategory.json,
133-
JSON.stringify(componentsByCategory, undefined, 2)
134-
);
135-
console.log('- Created index by category (JSON):', options.components.indexByCategory.json);
136-
137-
// Save by time (for later re-use)
138-
// Note that you should not rely, in general, on a specific key ordering.
139-
// Should you use that file later on, grab the keys as array, sort them, and iterate
140-
// const componentsSortedByTime = _(componentsByTime)
141-
// .toPairs()
142-
// .sortBy(0)
143-
// .reverse()
144-
// .fromPairs()
145-
// .value();
146-
// mkdirp.sync(path.dirname(options.components.paths.byTime));
147-
// fs.writeFileSync(
148-
// options.components.paths.byTime,
149-
// JSON.stringify(componentsSortedByTime, undefined, 2)
150-
// );
151-
// console.log('- Created components by time (JSON):', options.components.paths.byTime);
152-
153-
const templates = {
154-
analytics: fs.readFileSync(options.templates.analyticsPath, 'utf8'),
155-
footer: fs.readFileSync(options.templates.footerPath, 'utf8'),
156-
head: fs.readFileSync(options.templates.headPath, 'utf8'),
157-
header: fs.readFileSync(options.templates.headerPath, 'utf8'),
158-
componentsIndex: fs.readFileSync(options.templates.componentsIndexPath, 'utf8'),
159-
lazysizes: fs.readFileSync(options.templates.lazysizesPath, 'utf8'),
160-
};
161-
162-
const compiledPage = _.template(templates.componentsIndex)({
163-
title: 'Components',
164-
componentsByCategory,
15+
if (options.components.tempListPath === undefined ||
16+
!fs.existsSync(options.components.tempListPath)) {
17+
reject('Can not find components list (JSON)');
18+
return;
19+
}
20+
21+
const components = JSON.parse(fs.readFileSync(options.components.tempListPath, 'utf8'));
22+
23+
const templates = {
24+
analytics: fs.readFileSync(options.templates.analyticsPath, 'utf8'),
25+
footer: fs.readFileSync(options.templates.footerPath, 'utf8'),
26+
head: fs.readFileSync(options.templates.headPath, 'utf8'),
27+
header: fs.readFileSync(options.templates.headerPath, 'utf8'),
28+
componentsIndex: fs.readFileSync(options.templates.componentsIndexPath, 'utf8'),
29+
lazysizes: fs.readFileSync(options.templates.lazysizesPath, 'utf8'),
30+
};
31+
32+
Object.keys(options.components.index).forEach((key) => {
33+
const index = options.components.index[key];
34+
const componentsBySections = createSections(components, index, options);
35+
const compiledIndexPage = _.template(templates.componentsIndex)({
36+
index,
37+
componentsBySections,
16538
templates,
16639
options,
16740
});
168-
mkdirp.sync(path.dirname(options.components.indexByCategory.path));
169-
fs.writeFileSync(options.components.indexByCategory.path, compiledPage);
170-
console.log('- Created index by category:', options.components.indexByCategory.path);
41+
mkdirp.sync(path.dirname(index.path));
42+
fs.writeFileSync(index.path, compiledIndexPage);
43+
console.log(`- Created index "${index.title}":`, index.path);
44+
});
17145

172-
const elapsed = process.hrtime(startTime);
173-
console.log(chalk.magenta('Done with components index!'), chalk.dim(prettyHrtime(elapsed)));
174-
}).then(resolve).catch(reject); // generator
46+
const elapsed = process.hrtime(startTime);
47+
console.log(chalk.magenta('Done with components index!'), chalk.dim(prettyHrtime(elapsed)));
48+
resolve();
17549
}); // return promise

0 commit comments

Comments
 (0)