UNPKG

4.72 kBJavaScriptView Raw
1'use strict'
2
3const ware = require('ware')
4const fs = require('fs')
5const jade = require('jade')
6const join = require('path').join
7const assign = Object.assign
8
9const buildJs = require('./lib/build_js')
10const buildCss = require('./lib/build_css')
11const hash = require('./lib/hash')
12const eachCons = require('./lib/helpers/each_cons')
13const toArray = require('./lib/helpers/to_array')
14const memoize = require('./lib/memoize')
15const useCache = require('./lib/helpers/use_cache')
16
17/**
18 * Metalsmith middleware
19 */
20
21module.exports = function base (options) {
22 const ctx = {}
23
24 var app = ware()
25 .use(reset.bind(ctx))
26 .use(sortCss.bind(ctx))
27 .use(sortJs.bind(ctx))
28 .use(addJs.bind(ctx))
29 .use(addCss.bind(ctx))
30 .use(relayout.bind(ctx))
31
32 return function (files, ms, done) {
33 app.run(files, ms, done)
34 }
35}
36
37function reset (files, ms, done) {
38 this.styles = []
39 this.scripts = []
40 this.stylusImports = []
41 done()
42}
43
44/*
45 * Sorts out CSS into `styles` (local/external styles) and `stylusImports`
46 */
47
48function sortCss (files, ms, done) {
49 const list = toArray(ms.metadata().css)
50 const add = addAsset.bind(this, this.styles, 'css', files)
51
52 list.forEach((item) => {
53 if (item.match(/\.styl$/)) {
54 const path = join(ms.source(), item)
55 this.stylusImports.push(path)
56 } else {
57 add(item)
58 }
59 })
60
61 done()
62}
63
64function sortJs (files, ms, done) {
65 const list = toArray(ms.metadata().js)
66 const add = addAsset.bind(this, this.scripts, 'js', files)
67
68 list.forEach((item) => { add(item) })
69 done()
70}
71
72/**
73 * Internal: delegate of sortJs and sortCss.
74 * Adds a file into `this.styles` or `this.scripts`.
75 */
76
77function addAsset (list, what, files, item) {
78 const sources = files['_docpress.json'].sources
79
80 if (item.match(/^https?:\/\//)) {
81 list.push(item)
82 } else if (sources[item]) {
83 const local = sources[item]
84 list.push(local + '?t=' + hash(files[local].contents))
85 } else if (files[item]) {
86 list.push(item + '?t=' + hash(files[item].contents))
87 } else {
88 throw new Error(`${what}: can't find '${item}'`)
89 }
90}
91
92/**
93 * Assets
94 */
95
96function addCss (files, ms, done) {
97 const callback = (err, contents) => {
98 if (err) return done(err)
99 files['assets/style.css'] = { contents }
100 this.styles.unshift('assets/style.css?t=' + hash(contents))
101 done()
102 }
103
104 const cacheable = (this.stylusImports.length === 0)
105
106 ;(cacheable && useCache('cache/style.css', callback)) ||
107 buildCss({ imports: this.stylusImports }, callback)
108}
109
110/**
111 * Add JavaScript
112 */
113
114function addJs (files, ms, done) {
115 const callback = (err, contents) => {
116 if (err) return done(err)
117 files['assets/script.js'] = { contents }
118 this.scripts.push('assets/script.js?t=' +
119 hash(files['assets/script.js'].contents))
120 done()
121 }
122
123 useCache('cache/script.js', callback) ||
124 buildJs({}, callback)
125}
126
127/**
128 * Layout jade.
129 * Passes these template options:
130 *
131 * * `base` — prefix.
132 * * `toc` — the table of contents, as per toc.json.
133 * * `index` — the index, as per index.json.
134 * * `meta` — metalsmith metadata.
135 * * `prev.title` — previous page title.
136 * * `prev.url` — previous page url.
137 * * `next.title` — next page title.
138 * * `next.url` — next page url.
139 * * `active` — the filename of the active file (eg, `foo/index.html`)
140 * * `styles` — array of CSS files
141 * * `scripts` — array of JavaScript files
142 *
143 * `meta` typically has:
144 *
145 * * `github` (Optional)
146 * * `docs`
147 */
148
149function relayout (files, ms, done) {
150 const toc = files['_docpress.json'].toc
151 const index = files['_docpress.json'].index
152 const meta = ms.metadata()
153
154 const jadeData = fs.readFileSync(join(__dirname, 'data/layout.jade'), 'utf-8')
155 const layout = memoize(['jade', jadeData], () => {
156 return jade.compile(jadeData, { pretty: true })
157 })
158
159 eachCons(index, (_, fname, __, prevName, ___, nextName) => {
160 if (!fname.match(/\.html$/)) return
161 const file = files[fname]
162 const base = Array(fname.split('/').length).join('../')
163 const styles = this.styles.map(relativize(base))
164 const scripts = this.scripts.map(relativize(base))
165
166 const locals = {
167 base, toc, index, meta, styles, scripts,
168 prev: prevName && assign({}, index[prevName], { url: base + prevName }),
169 next: nextName && assign({}, index[nextName], { url: base + nextName }),
170 active: fname
171 }
172
173 const key = [ jadeData, locals, file ]
174
175 file.contents = memoize(['jadedata', key], () => {
176 return layout(assign({}, file, locals))
177 })
178 })
179
180 done()
181}
182
183function relativize (base) {
184 return function (url) {
185 if (url.substr(0, 1) === '/' || url.match(/^https?:\/\//)) {
186 return url
187 }
188
189 return base + url
190 }
191}