1 | const
|
2 | path = require('path'),
|
3 | fs = require('fs'),
|
4 | express = require('express'),
|
5 | sgUtil = require('./util');
|
6 |
|
7 | class 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 |
|
150 | module.exports = DevServer; |
\ | No newline at end of file |