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 | |
10 |
|
11 |
|
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';
|
30 | import express from 'express';
|
31 | import fs from 'fs-extra';
|
32 | import glob from 'glob-promise';
|
33 | import path from 'path';
|
34 | import debounce from 'lodash.debounce';
|
35 | import WebSocket from 'ws';
|
36 |
|
37 | function injectHotReloadScript(v, port) {
|
38 | return typeof v === 'string'
|
39 | ? v.replace(
|
40 | '</body>',
|
41 | `
|
42 | <script>new WebSocket("ws://localhost:80").addEventListener("message", event => {
|
43 | if (event.data === "reload") window.location.reload();
|
44 | })</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.<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.<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<void>}
|
86 | */
|
87 |
|
88 | const mosaic = (config) => {
|
89 | let app, routes, socket;
|
90 |
|
91 | const hotReload = config.serve && 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 && 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 |
|
216 | export 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>
|