UNPKG

6.02 kBHTMLView Raw
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <title>JSDoc: Source: index.js</title>
6
7 <script src="scripts/prettify/prettify.js"> </script>
8 <script src="scripts/prettify/lang-css.js"> </script>
9 <!--[if lt IE 9]>
10 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
11 <![endif]-->
12 <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
13 <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
14</head>
15
16<body>
17
18<div id="main">
19
20 <h1 class="page-title">Source: index.js</h1>
21
22
23
24
25
26
27 <section>
28 <article>
29 <pre class="prettyprint source linenums"><code>import chokidar from 'chokidar';
30import express from 'express';
31import fs from 'fs-extra';
32import glob from 'glob-promise';
33import path from 'path';
34import debounce from 'lodash.debounce';
35import WebSocket from 'ws';
36
37function injectHotReloadScript(v, port) {
38 return typeof v === 'string'
39 ? v.replace(
40 '&lt;/body>',
41 `
42 &lt;script>new WebSocket("ws://localhost:80").addEventListener("message", event => {
43 if (event.data === "reload") window.location.reload();
44 })&lt;/script>
45 `
46 )
47 : v;
48}
49
50/**
51 * @typedef {object} FileObject
52 * @property {string} filepath - the location of the file on disk
53 * @property {string} content - the file content
54 */
55
56/**
57 *
58 * @typedef {object} Server
59 * @property {string} [port=3000] - the port to serve on
60 * @property {string} [staticPath] - An optional static path
61 * @property {Object.&lt;string, string>} routes - A dictionary of key value pairs, where each key is the name of a route path, and the value is the string content that should be served for that route
62 *
63 */
64
65/**
66 * @typedef OutputMapFn
67 *
68 * @returns {FileObject[]}
69 */
70
71/**
72 *
73 * @typedef {Object} Config - Mosaic configuration
74 * @property {Object.&lt;string,string>} input - a dictionary of key value pairs where key is the name of a set of files and value is a glob pattern describing the location of those files on disk
75 * @property {function[]} transform - an array of functions to be called in series in order to transform the input
76 * @property {OutputMapFn} output - a function that takes the output of the transform pipeline and then returns an FileObject array representing the files to be saved to disk
77 * @property {Server} [serve] - optionally serve files using an express server
78 *
79 */
80
81/**
82 *
83 * @param {Config} config
84 *
85 * @returns {Promise&lt;void>}
86 */
87
88const mosaic = (config) => {
89 let app, routes, socket;
90
91 const hotReload = config.serve &amp;&amp; config.watch;
92
93 if (hotReload) {
94 const wss = new WebSocket.Server({ port: 80 });
95 wss.on('connection', (s) => {
96 socket = s;
97 });
98 }
99
100 if (config.serve) {
101 let { port = 3000, staticPath = './' } = config.serve;
102 app = new express();
103 app.use(express.static(staticPath));
104 app.use(function (req, res, next) {
105 let k = req.originalUrl.split('?')[0];
106 if (k in routes) {
107 let payload = routes[k];
108
109 if (hotReload) {
110 payload = injectHotReloadScript(payload);
111 }
112
113 res.send(payload);
114 } else {
115 next();
116 }
117 });
118 app.listen(port, () =>
119 console.log(
120 `Express server listening on port ${port}`
121 )
122 );
123 }
124
125 const cache = {
126 /*
127 [mdContent]: [{ filepath, content }, { filepath, content }],
128 [template]: [{ filepath, content }]
129 */
130 };
131
132 const getCacheValues = () =>
133 Object.keys(cache).reduce((a, k) => {
134 let isGlob = config.input[k].includes('*');
135 let values = Object.values(cache[k]);
136 a[k] = isGlob ? values : values[0];
137 return a;
138 }, {});
139
140 const update = () => {
141 let values = getCacheValues();
142 let { transform = [] } = config;
143
144 let transformResult = transform.reduce(
145 (output, fn) => fn(output),
146 values
147 );
148
149 if (config.serve) {
150 let { mapRoutes = () => ({}) } = config.serve;
151 routes = mapRoutes(transformResult);
152 if (hotReload &amp;&amp; socket) socket.send('reload');
153 }
154
155 if (config.output) {
156 let { map = (v = v) } = config.output;
157 let output = map(transformResult);
158
159 return Promise.all(
160 output.map(({ filepath, content }) => {
161 return fs
162 .ensureDir(path.dirname(filepath))
163 .then(() =>
164 fs.promises.writeFile(filepath, content)
165 );
166 })
167 );
168 } else {
169 return Promise.resolve();
170 }
171 };
172
173 const updateCache = (key, filepath) =>
174 fs.promises
175 .readFile(filepath, 'utf8')
176 .then((content) => {
177 cache[key][filepath] = { content, filepath };
178 });
179
180 const populateCache = () =>
181 Promise.all(
182 Object.entries(config.input).map(
183 ([key, globPath]) => {
184 cache[key] = {};
185 return glob(globPath).then((paths) =>
186 Promise.all(
187 paths.map((f) => updateCache(key, f))
188 )
189 );
190 }
191 )
192 );
193
194 const scheduleUpdate = debounce(update);
195
196 return populateCache()
197 .then(update)
198 .then(() => {
199 if (config.watch) {
200 Object.entries(config.input).forEach(
201 ([key, glob]) => {
202 chokidar
203 .watch(glob)
204 .on('change', (filepath) => {
205 updateCache(key, filepath).then(
206 scheduleUpdate
207 );
208 });
209 }
210 );
211 //@TODO: on('unlink').on('add')
212 }
213 });
214};
215
216export default mosaic;
217</code></pre>
218 </article>
219 </section>
220
221
222
223
224</div>
225
226<nav>
227 <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#mosaic">mosaic</a></li></ul>
228</nav>
229
230<br class="clear">
231
232<footer>
233 Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Tue Feb 02 2021 21:18:41 GMT+0000 (Greenwich Mean Time)
234</footer>
235
236<script> prettyPrint(); </script>
237<script src="scripts/linenumber.js"> </script>
238</body>
239</html>