UNPKG

3.16 kBJavaScriptView Raw
1import chokidar from 'chokidar';
2import express from 'express';
3import fs from 'fs-extra';
4import glob from 'glob-promise';
5import path from 'path';
6import debounce from 'lodash.debounce';
7import WebSocket from 'ws';
8
9function injectHotReloadScript(v, port) {
10 return typeof v === 'string'
11 ? v.replace(
12 '</body>',
13 `
14 <script>new WebSocket("ws://localhost:80").addEventListener("message", event => {
15 if (event.data === "reload") window.location.reload();
16 })</script>
17 `
18 )
19 : v;
20}
21
22const mosaic = (config) => {
23 let app, routes, socket;
24
25 const hotReload = config.serve && config.watch;
26
27 if (hotReload) {
28 const wss = new WebSocket.Server({ port: 80 });
29 wss.on('connection', (s) => {
30 socket = s;
31 });
32 }
33
34 if (config.serve) {
35 let { port = 3000, staticPath = './' } = config.serve;
36 app = new express();
37 app.use(express.static(staticPath));
38 app.use(function (req, res, next) {
39 let k = req.originalUrl.split('?')[0];
40 if (k in routes) {
41 let payload = routes[k];
42
43 if (hotReload) {
44 payload = injectHotReloadScript(payload);
45 }
46
47 res.send(payload);
48 } else {
49 next();
50 }
51 });
52 app.listen(port, () => console.log(`Express server listening on port ${port}`));
53 }
54
55 const cache = {};
56
57 const getCacheValues = () =>
58 Object.keys(cache).reduce((a, k) => {
59 let isGlob = config.input[k].includes('*');
60 let values = Object.values(cache[k]);
61 a[k] = isGlob ? values : values[0];
62 return a;
63 }, {});
64
65 const update = () => {
66 let values = getCacheValues();
67 let { transform = [] } = config;
68
69 let transformResult = transform.reduce((output, fn) => fn(output), values);
70
71 if (config.serve) {
72 let { mapRoutes = () => ({}) } = config.serve;
73 routes = mapRoutes(transformResult);
74 if (hotReload && socket) socket.send('reload');
75 }
76
77 if (config.output) {
78 let { map = (v = v) } = config.output;
79 let output = map(transformResult);
80
81 return Promise.all(
82 output.map(({ filepath, content }) => {
83 return fs
84 .ensureDir(path.dirname(filepath))
85 .then(() => fs.promises.writeFile(filepath, content));
86 })
87 );
88 } else {
89 return Promise.resolve();
90 }
91 };
92
93 const updateCache = (key, filepath) =>
94 fs.promises.readFile(filepath, 'utf8').then((content) => {
95 cache[key][filepath] = { content, filepath };
96 });
97
98 const populateCache = () =>
99 Promise.all(
100 Object.entries(config.input).map(([key, globPath]) => {
101 cache[key] = {};
102 return glob(globPath).then((paths) => Promise.all(paths.map((f) => updateCache(key, f))));
103 })
104 );
105
106 const scheduleUpdate = debounce(update);
107
108 return populateCache()
109 .then(update)
110 .then(() => {
111 if (config.watch) {
112 Object.entries(config.input).forEach(([key, glob]) => {
113 chokidar.watch(glob).on('change', (filepath) => {
114 updateCache(key, filepath).then(scheduleUpdate);
115 });
116 });
117 //@todo: handle unlink, add
118 }
119 });
120};
121
122export default mosaic;