UNPKG

4.11 kBJavaScriptView Raw
1const
2 path = require('path'),
3 fs = require('fs'),
4 express = require('express'),
5 sgUtil = require('./util');
6
7class DevServer {
8
9 constructor({conf, CollectorStore: {getSections, getUrls, getCollectedData}}) {
10
11 this.conf = conf;
12 this.getSections = getSections;
13 this.getUrls = getUrls;
14 this.getCollectedData = getCollectedData;
15
16 this.name = conf.get('package.name');
17 this.htmlExt = conf.get('htmlExt');
18 this.viewerRootPath = conf.get('app.viewerRootPath');
19 this.baseDir = conf.get('dest');
20
21 this.app = express();
22 this.enableNoCache();
23 this.allowCrossOriginAccess();
24 this.handleTrailinglessRequests();
25 }
26
27 start(cb) {
28
29 this.bsConfig = this.conf.util.extendDeep({}, {
30 server: {baseDir: this.baseDir},
31 port: 3000,
32 reloadOnRestart: true,
33 notify: false,
34 https: false,
35 open: true,
36 startPath: null,
37 reloadDelay: 0,
38 reloadDebounce: 0,
39 injectChanges: true,
40 middleware: [this.app],
41 logLevel: 'info',
42 logPrefix: 'BS',
43 logConnections: false,
44 logFileChanges: true,
45 logSnippet: true,
46 snippetOptions: {
47 rule: {
48 match: /<\/head>/i,
49 fn: function (snippet, match) {
50 return snippet + match;
51 }
52 }
53 }
54 }, this.conf.get('server'));
55
56 this.instanciate().init(this.bsConfig, cb);
57
58 return this;
59 }
60
61 instanciate() {
62 const browserSync = require(this.getBrowserSyncPath());
63 this.browserSync = browserSync[browserSync.has(this.name) ? 'get' : 'create'](this.name);
64 return this.browserSync;
65 }
66
67 getBrowserSyncPath() {
68 const parentBrowserSyncPath = path.join(process.cwd(), 'node_modules', 'browser-sync');
69 return fs.existsSync(parentBrowserSyncPath) ? parentBrowserSyncPath : 'browser-sync';
70 }
71
72 addRoutes() {
73 const {getSections, getUrls} = this;
74
75 getSections().forEach((section) => {
76
77 this.app.get(section.route, async (req, res) => {
78 const
79 ext = sgUtil.getFileExtension(req.url),
80 type = sgUtil.getFileExtension(sgUtil.removeFileExtension(req.url)) || ext,
81 basePath = type === ext ? sgUtil.removeFileExtension(req.url) : sgUtil.removeFileExtension(sgUtil.removeFileExtension(req.url), true);
82
83 if (getUrls().has(basePath)) {
84 const
85 file = getUrls().get(basePath),
86 renderOutput = await file.render;
87
88 res.send(renderOutput[type]);
89 }
90 });
91 });
92
93 this.app.get(['/', `/${this.viewerRootPath}`], (req, res) => {
94 res.writeHead(301, {
95 Location: `${req.socket.encrypted ? 'https' : 'http'}://${req.headers.host}/${this.viewerRootPath}/index.html`
96 });
97 res.end();
98 });
99
100 this.app.get(`/${this.viewerRootPath}/structure.json`, (req, res) => {
101 res.json(this.getCollectedData());
102 res.end();
103 });
104 }
105
106 enableNoCache() {
107 this.app.use((req, res, next) => {
108 res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
109 res.setHeader('Expires', '-1');
110 res.setHeader('Pragma', 'no-cache');
111 next();
112 });
113 }
114
115 allowCrossOriginAccess() {
116 this.app.use((req, res, next) => {
117 res.setHeader('Access-Control-Allow-Origin', '*');
118 next();
119 });
120 }
121
122 handleTrailinglessRequests() {
123 this.app.use((req, res, next) => {
124 const {pathname, path} = req._parsedUrl;
125
126 if (pathname.substr(-1) !== '/' && pathname.length > 1 && pathname.match(/\/[^.\/]+$/)) {
127 const newPath = path.replace(new RegExp(pathname), `${pathname}/`);
128 res.writeHead(301, {
129 Location: `${req.socket.encrypted ? 'https' : 'http'}://${req.headers.host}${newPath}`
130 });
131 res.end();
132 }
133 else {
134 next();
135 }
136 });
137 }
138
139 rewriteUrls(url) {
140 const {urlRewriteRules = {}} = this.bsConfig;
141 urlRewriteRules[`/([^/]+)/$`] = `/$1/$1.${this.htmlExt}`;
142
143 Object.keys(urlRewriteRules).map((key) => {
144 url = url.replace(new RegExp(key, 'i'), urlRewriteRules[key]);
145 });
146 return url;
147 }
148}
149
150module.exports = DevServer;
\No newline at end of file