Skip to content

Commit ff2ffe2

Browse files
author
Adam Morse
authored
Merge pull request tachyons-css#131 from sebastienbarre/master
Recent Components page, RSS feed
2 parents 4b787bb + 38dfca4 commit ff2ffe2

17 files changed

+6742
-751
lines changed

build.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ 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');
20+
const componentsBuildRSS = require('./src/components-build-rss');
1921
const componentsBuildPages = require('./src/components-build-pages');
2022
const componentsBuildScreenshots = require('./src/components-build-screenshots');
2123

2224
// See src/components-build-defaults for list of options that can be overriden
2325
const options = {
2426
components: {
25-
globPattern: 'src/components/banners/*.html',
27+
// globPattern: 'src/components/article**/*.html',
2628
// frontMatter: {
2729
// bodyClass: 'bg-red',
2830
// screenshot: {
@@ -35,20 +37,22 @@ const options = {
3537
// },
3638
};
3739

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
40+
// Note that componentsBuildList() generates a temporary components list (saved as JSON),
41+
// which is re-used by later steps (for performance).
42+
// If you are working on one component, uncomment and set components.GlobPattern
43+
// in the options above to only (re-)generate the list, index, pages, and screenshots for the
44+
// corresponding category. Once you are done, comment components.GlobPattern back, comment
4345
// 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
46+
// re-create the full index pages for all components, re-generate all the pages (since they need
4547
// to cross-reference the new component), and use the previously generated screenshots as well
4648
// as the new one (i.e. no need to re-generate *all* the screenshots unless you made
4749
// modifications to the screenshots script itself).
4850
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
51+
yield componentsBuildList(options); // <- builds temporary components list (JSON)
52+
yield componentsBuildIndex(options); // <- builds index pages (by category & most recent)
53+
yield componentsBuildRSS(options); // <- builds RSS feed
54+
yield componentsBuildPages(options); // <- comment to skip building pages
55+
yield componentsBuildScreenshots(options); // <- comment to skip building screenshots
5256
}).then(() => {
5357
const elapsed = process.hrtime(startTime);
5458
console.log(chalk.green('All done'), chalk.dim(prettyHrtime(elapsed)));

package.json

+19-20
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,7 @@
3636
"url": "https://github.com/tachyons-css/tachyons-css.github.io/issues"
3737
},
3838
"dependencies": {
39-
"chalk": "^1.1.3",
40-
"co": "^4.6.0",
41-
"express": "^4.14.0",
42-
"filesize": "^3.3.0",
43-
"imagemin": "^5.2.2",
44-
"imagemin-mozjpeg": "^6.0.0",
45-
"jimp": "^0.2.27",
46-
"lazysizes": "^3.0.0-rc2",
47-
"nightmare": "^2.8.1",
48-
"pretty-hrtime": "^1.0.3",
39+
"tachyons": "^4.5.4",
4940
"tachyons-background-size": "^5.0.3",
5041
"tachyons-base": "^1.2.5",
5142
"tachyons-border-colors": "^4.2.2",
@@ -62,7 +53,7 @@
6253
"tachyons-debug": "^1.1.7",
6354
"tachyons-debug-grid": "^1.2.0",
6455
"tachyons-display": "^5.0.1",
65-
"tachyons-flexbox": "^2.0.0",
56+
"tachyons-flexbox": "^2.0.4",
6657
"tachyons-floats": "^3.0.1",
6758
"tachyons-font-family": "^4.3.0",
6859
"tachyons-font-style": "^4.0.1",
@@ -76,11 +67,14 @@
7667
"tachyons-links": "^3.0.6",
7768
"tachyons-lists": "^2.0.9",
7869
"tachyons-max-widths": "^4.0.1",
70+
"tachyons-modules": "^1.1.8",
7971
"tachyons-opacity": "^1.1.5",
8072
"tachyons-outlines": "^1.0.0",
8173
"tachyons-overflow": "^4.0.1",
8274
"tachyons-position": "^6.0.1",
75+
"tachyons-queries": "^0.3.1",
8376
"tachyons-skins": "^4.0.0",
77+
"tachyons-skins-pseudo": "^1.0.1",
8478
"tachyons-spacing": "^6.0.1",
8579
"tachyons-tables": "^1.0.4",
8680
"tachyons-text-align": "^3.0.1",
@@ -94,31 +88,40 @@
9488
"tachyons-white-space": "^4.0.1",
9589
"tachyons-widths": "^5.0.1",
9690
"tachyons-word-break": "3.0.1",
97-
"tachyons-z-index": "^1.0.4",
98-
"tmp": "0.0.31"
91+
"tachyons-z-index": "^1.0.4"
9992
},
10093
"devDependencies": {
10194
"autoprefixer": "^6.5.1",
10295
"browser-sync": "^2.17.5",
10396
"camelize": "^1.0.0",
97+
"chalk": "^1.1.3",
98+
"co": "^4.6.0",
10499
"color": "^0.11.3",
105100
"colorable": "^1.0.5",
106101
"css": "^2.2.1",
107102
"css-mqpacker": "^5.0.1",
108103
"css-parse": "^2.0.0",
109104
"cssstats": "^3.0.0-beta.2",
110105
"escape-html": "^1.0.3",
106+
"express": "^4.14.0",
111107
"filesize": "^3.3.0",
112108
"get-classes-from-html": "^1.0.1",
113109
"glob": "^7.1.1",
114110
"gzip-size": "^3.0.0",
111+
"imagemin": "^5.2.2",
112+
"imagemin-mozjpeg": "^6.0.0",
115113
"immutable-css": "^1.1.2",
116114
"is-blank": "^1.1.0",
117115
"is-css-root": "^1.0.1",
116+
"jimp": "^0.2.27",
118117
"json-front-matter": "^1.0.0",
118+
"lazysizes": "^3.0.0-rc2",
119119
"lodash": "^4.16.6",
120120
"mkdirp": "^0.5.1",
121+
"moment": "^2.17.1",
122+
"nightmare": "^2.8.1",
121123
"node-minify": "^1.3.9",
124+
"nodegit": "^0.16.0",
122125
"normalize": "^0.3.1",
123126
"perfectionist": "^2.3.1",
124127
"pixrem": "^3.0.2",
@@ -132,16 +135,12 @@
132135
"postcss-discard-empty": "^2.1.0",
133136
"postcss-import": "^8.1.2",
134137
"postcss-select": "^2.1.0",
138+
"pretty-hrtime": "^1.0.3",
135139
"remove-html-extension": "0.0.1",
140+
"rss": "^1.2.1",
136141
"s3": "^4.4.0",
137-
"tachyons": "^4.5.4",
138-
"tachyons-cli": "^1.0.9",
139-
"tachyons-debug-grid": "^1.2.0",
140-
"tachyons-flexbox": "^2.0.1",
141-
"tachyons-modules": "^1.1.8",
142-
"tachyons-queries": "^0.3.1",
143-
"tachyons-skins-pseudo": "^1.0.1",
144142
"titleize": "^1.0.0",
143+
"tmp": "0.0.31",
145144
"watch": "^1.0.1"
146145
},
147146
"contributors": [

src/components-build-defaults.js

+68-21
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,80 @@
1+
const moment = require('moment');
2+
const titleize = require('titleize');
3+
4+
const cleanTitleize = str => titleize(str.replace(/(_|-)/g, ' '));
5+
const creationTimeToYMD = c => moment(c.creationTime).format('YYYY-MM-DD');
6+
17
module.exports = {
8+
siteUrl: 'http://tachyons.io', // needed for RSS feed
9+
siteDescription: 'Tachyons. Functional CSS for humans.',
210
// Components
311
components: {
4-
globPattern: 'src/components/**/*.html', // source components to process
5-
forNavPath: 'tmp/componentsForNav.json', // temporary file built by the index
6-
indexPath: 'components/index.html', // target location of components index
7-
buildPages: true, // false to skip building pages
8-
buildScreenshots: true, // false to skip building screenshots
9-
frontMatter: { // font matter defaults (i.e. optional)
10-
name: undefined, // undefined to infer component name
11-
title: undefined, // undefined to infer component title
12-
bodyClass: 'bg-white', // class to apply on <body>
13-
screenshot: { // per-component screenshot options
14-
selector: '[data-name="component"]', // DOM element to capture
15-
autocrop: true, // autocrop the capture
16-
'background-position': 'center center', // CSS background position
17-
'background-size': 'cover', // CSS background size ('cover', 'contain', 'auto')
12+
globPattern: 'src/components/**/*.html', // source components to process
13+
tempListPath: 'tmp/components.json', // temporary JSON file built by the index
14+
build: {
15+
pages: true, // false to skip building pages
16+
screenshots: true, // false to skip building screenshots
17+
},
18+
index: { // list index pages to build
19+
byCategory: { // components by category
20+
title: 'Components',
21+
path: 'components/index.html', // target location of index by category
22+
sortAllBy: [['src'], ['asc']], // sort by file location will do
23+
limitAll: false, // use all components
24+
createSectionsBy: 'category', // create a section for each category
25+
showSectionsTOC: true, // show Table of Contents (e.g. categories)
26+
},
27+
mostRecent: { // most recent components
28+
title: 'Recent Components',
29+
path: 'components/recent.html', // target location of recent index
30+
sortAllBy: [['creationTime'], ['desc']], // sort by most recent component first
31+
limitAll: 50, // use the 50 most recent ones
32+
createSectionsBy: creationTimeToYMD, // group by day
33+
prettifySection: v => moment(v).format('LL'), // display as day
34+
showSectionsTOC: false, // no need for Table of Contents
35+
},
36+
},
37+
rss: { // RSS feed
38+
title: 'Tachyons Recent Components',
39+
categories: ['CSS', 'Functional CSS'], // Categories this feed belongs to
40+
ttl: 60, // Number of mins feed can be cached
41+
path: 'components/rss.xml', // target location of feed (sync head.html)
42+
count: 20, // how many in feed
43+
},
44+
page: { // options related to each component page
45+
composeTitle: (category, name) => `${category} | ${name}`, // compose title from cat, name
46+
},
47+
prettify: {
48+
id: cleanTitleize, // prettify component id into component name
49+
category: cleanTitleize, // prettify component category
50+
creationTime: v => moment(v).format('LLL'), // prettify component creation time
51+
},
52+
frontMatter: { // font matter defaults (i.e. optional)
53+
name: undefined, // undefined to infer component name
54+
title: undefined, // undefined to infer component title
55+
bodyClass: 'bg-white', // class to apply on <body>
56+
screenshot: { // per-component screenshot options
57+
selector: '[data-name="component"]', // DOM element to capture
58+
autocrop: true, // autocrop the capture
59+
'background-position': 'center center', // CSS bg position
60+
'background-size': 'cover', // CSS bg size ('cover', 'contain', 'auto')
1861
},
1962
},
2063
},
2164
// Screenshot options
2265
// (components only for now, but could apply to other areas for consistency)
2366
screenshot: {
24-
basename: 'screenshot.jpg', // name of *JPEG* screenshot
25-
aspectRatio: '4x3', // Tachyons aspect ratio of screenshots
26-
viewportWidth: 1024, // viewport width used for capture
27-
viewportHeight: 768, // viewport height used for capture
28-
targetMinWidth: 400, // min width of target (final) screenshot
29-
targetMinHeight: 160, // min height of target (final) screenshot
30-
mozjpegQuality: 90, // mozjpeg optimizer quality (default 75)
67+
basename: 'screenshot.jpg', // name of *JPEG* screenshot
68+
aspectRatio: '4x3', // Tachyons aspect ratio of screenshots
69+
viewport: {
70+
width: 1024, // viewport width used for capture
71+
height: 768, // viewport height used for capture
72+
},
73+
target: {
74+
minWidth: 400, // min width of target (final) screenshot
75+
minHeight: 160, // min height of target (final) screenshot
76+
},
77+
mozjpegQuality: 90, // mozjpeg optimizer quality (default 75)
3178
},
3279
// Misc
3380
tachyonsCssPath: 'src/css/tachyons.css',

src/components-build-index.js

+34-91
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,49 @@
11
const _ = require('lodash');
22
const chalk = require('chalk');
3-
const crypto = require('crypto');
4-
const fm = require('json-front-matter');
53
const fs = require('fs');
6-
const glob = require('glob');
74
const mkdirp = require('mkdirp');
85
const path = require('path');
96
const prettyHrtime = require('pretty-hrtime');
10-
const rmHtmlExt = require('remove-html-extension');
11-
const titleize = require('titleize');
127

138
const defaults = require('./components-build-defaults');
14-
15-
const getTitle = (component) => {
16-
const title = rmHtmlExt(component).replace('src/components/', '').replace(/(\/|_|-)/g, ' ');
17-
return titleize(title);
18-
};
19-
20-
const getName = component => titleize(getTitle(component.split('/')[3]));
9+
const createSections = require('./components-build-sections');
2110

2211
module.exports = _options => new Promise((resolve, reject) => {
2312
const options = _.merge({}, defaults, _options);
2413
const startTime = process.hrtime();
25-
glob(options.components.globPattern, {}, (err, components) => {
26-
console.log(chalk.magenta('Working on components index...'));
27-
if (err) {
28-
reject(err);
29-
return;
30-
}
31-
32-
const npmPackage = JSON.parse(fs.readFileSync('package.json', 'utf8'));
33-
34-
const componentsForNav = {};
35-
components.forEach((component) => {
36-
const componentTokens = component.replace('src/components/', '').split('/');
37-
const category = componentTokens[0];
38-
39-
const componentHtml = fs.readFileSync(component, 'utf8');
40-
const fmParsed = fm.parse(componentHtml);
41-
const srcFrontMatter = fmParsed.attributes || {};
42-
const frontMatter = _.merge({}, options.components.frontMatter, srcFrontMatter);
43-
const dir = component.replace('src/', '').replace('.html', '');
44-
45-
// Compute component signature based on the Tachyons version and the contents of the
46-
// component itself. This can be used to bust the browser cache of screenshots.
47-
const md5sum = crypto.createHash('md5');
48-
md5sum.update(npmPackage.version);
49-
md5sum.update(componentHtml);
50-
const signature = md5sum.digest('hex');
51-
52-
componentsForNav[category] = componentsForNav[category] || [];
53-
componentsForNav[category].push({
54-
name: frontMatter.name || getName(component),
55-
title: frontMatter.title || getTitle(component),
56-
src: component,
57-
path: `${dir}/index.html`,
58-
href: `/${dir}/index.html`,
59-
screenshot: {
60-
path: `${dir}/${options.screenshot.basename}`,
61-
href: `/${dir}/${options.screenshot.basename}?version=${signature}`,
62-
},
63-
signature,
64-
// This is the raw front matter, as found in the component source, NOT merged
65-
// with any defaults, so that it is easier to spot the overrides.
66-
// It is up to build scripts to merge with options.components.frontMatter down the road.
67-
frontMatter: srcFrontMatter,
68-
});
69-
});
70-
71-
const categories = Object.keys(componentsForNav);
72-
console.log(
73-
'- Found', components.length, components.length > 1 ? 'components' : 'component',
74-
'in', categories.length, categories.length > 1 ? 'categories' : 'category'
75-
);
76-
77-
mkdirp.sync(path.dirname(options.components.forNavPath));
78-
fs.writeFileSync(options.components.forNavPath, JSON.stringify(componentsForNav, undefined, 2));
79-
console.log('- Created navigation JSON:', options.components.forNavPath);
80-
81-
const analytics = fs.readFileSync(options.templates.analyticsPath, 'utf8');
82-
const footer = fs.readFileSync(options.templates.footerPath, 'utf8');
83-
const head = fs.readFileSync(options.templates.headPath, 'utf8');
84-
const header = fs.readFileSync(options.templates.headerPath, 'utf8');
85-
const componentsIndexTemplate = fs.readFileSync(options.templates.componentsIndexPath, 'utf8');
86-
const lazysizesTemplate = fs.readFileSync(options.templates.lazysizesPath, 'utf8');
87-
88-
const compiledPage = _.template(componentsIndexTemplate)({
89-
componentsForNav,
90-
title: 'Components',
91-
analytics,
92-
footer,
93-
head,
94-
header,
95-
lazysizesTemplate,
14+
console.log(chalk.magenta('Working on components index...'));
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,
38+
templates,
9639
options,
9740
});
98-
mkdirp.sync(path.dirname(options.components.indexPath));
99-
fs.writeFileSync(options.components.indexPath, compiledPage);
100-
console.log('- Created index:', options.components.indexPath);
101-
102-
const elapsed = process.hrtime(startTime);
103-
console.log(chalk.magenta('Done with components index!'), chalk.dim(prettyHrtime(elapsed)));
104-
resolve();
105-
}); // glob
41+
mkdirp.sync(path.dirname(index.path));
42+
fs.writeFileSync(index.path, compiledIndexPage);
43+
console.log(`- Created index "${index.title}":`, index.path);
44+
});
45+
46+
const elapsed = process.hrtime(startTime);
47+
console.log(chalk.magenta('Done with components index!'), chalk.dim(prettyHrtime(elapsed)));
48+
resolve();
10649
}); // return promise

0 commit comments

Comments
 (0)