UNPKG

11.6 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
8
9var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
10
11var _keys = require('babel-runtime/core-js/object/keys');
12
13var _keys2 = _interopRequireDefault(_keys);
14
15var _path = require('path');
16
17var _fsExtra = require('fs-extra');
18
19var _url = require('url');
20
21var _path2 = require('./utils/path');
22
23var _constants = require('./constants');
24
25var _composeMiddleware = require('compose-middleware');
26
27var _express = require('express');
28
29var _serveStatic = require('serve-static');
30
31var _serveStatic2 = _interopRequireDefault(_serveStatic);
32
33var _serveFavicon = require('serve-favicon');
34
35var _serveFavicon2 = _interopRequireDefault(_serveFavicon);
36
37var _compression = require('compression');
38
39var _compression2 = _interopRequireDefault(_compression);
40
41var _ssr = require('./middlewares/ssr');
42
43var _ssr2 = _interopRequireDefault(_ssr);
44
45var _koaError = require('./middlewares/koa-error');
46
47var _koaError2 = _interopRequireDefault(_koaError);
48
49var _expressError = require('./middlewares/express-error');
50
51var _expressError2 = _interopRequireDefault(_expressError);
52
53var _static = require('./middlewares/static');
54
55var _static2 = _interopRequireDefault(_static);
56
57function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
58
59// enum of internal middlewares
60/**
61 * @file MiddlewareComposer.js
62 * @author lavas
63 */
64
65const INTERNAL_MIDDLEWARE = {
66 TRAILING_SLASH: 'trailing-slash',
67 STATIC: 'static',
68 SERVICE_WORKER: 'service-worker',
69 FAVICON: 'favicon',
70 COMPRESSION: 'compression',
71 SSR: 'ssr',
72 ERROR: 'error'
73};
74
75const ALL_MIDDLEWARES = (0, _keys2.default)(INTERNAL_MIDDLEWARE).map(key => INTERNAL_MIDDLEWARE[key]);
76
77class MiddlewareComposer {
78 constructor(core) {
79 this.core = core;
80 this.cwd = core.cwd;
81 this.config = core.config;
82 this.isProd = core.isProd;
83 this.internalMiddlewares = [];
84 }
85
86 add(middleware, head = false) {
87 if (typeof middleware !== 'function') {
88 throw new Error('Middleware must be a function.');
89 }
90 if (head) {
91 this.internalMiddlewares.unshift(middleware);
92 } else {
93 this.internalMiddlewares.push(middleware);
94 }
95 }
96
97 reset(config) {
98 this.config = config;
99 this.internalMiddlewares = [];
100 }
101
102 /**
103 * setup some internal middlewares
104 *
105 */
106 setup() {
107 if (this.config.build && this.config.build.compress) {
108 // gzip compression
109 this.add((0, _compression2.default)());
110 }
111 // serve favicon
112 let faviconPath = _path.posix.join(this.cwd, _constants.ASSETS_DIRNAME_IN_DIST, 'img/icons/favicon.ico');
113 this.add((0, _serveFavicon2.default)(faviconPath));
114 }
115
116 /**
117 * compose middlewares into a chain.
118 * NOTE: MUST be called in Node >= 7.6.0 because of "async" syntax.
119 *
120 * @param {Array<Function>|Function} selectedMiddlewares middlewares selected by user
121 * @return {Function} koaMiddleware
122 */
123 koa(selectedMiddlewares = ALL_MIDDLEWARES) {
124 var _this = this;
125
126 if (!Array.isArray(selectedMiddlewares)) {
127 selectedMiddlewares = [selectedMiddlewares];
128 }
129
130 const composeKoa = require('koa-compose');
131 const c2k = require('koa-connect');
132 const mount = require('koa-mount');
133 const koaStatic = require('koa-static');
134 const send = require('koa-send');
135
136 let { router: { base }, build: { ssr, publicPath }, serviceWorker, errorHandler } = this.config;
137 base = (0, _path2.removeTrailingSlash)(base || '/');
138
139 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.COMPRESSION)) {
140 // gzip compression
141 this.add((0, _compression2.default)());
142 }
143
144 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.FAVICON)) {
145 // serve favicon
146 let faviconPath = _path.posix.join(this.cwd, _constants.ASSETS_DIRNAME_IN_DIST, 'img/icons/favicon.ico');
147 if ((0, _fsExtra.existsSync)(faviconPath)) {
148 this.add((0, _serveFavicon2.default)(faviconPath));
149 }
150 }
151
152 // transform express/connect style middleware to koa style
153 this.internalMiddlewares = this.internalMiddlewares.map(c2k);
154
155 // koa defaults to 404 when it sees that status is unset
156 this.add((() => {
157 var _ref = (0, _asyncToGenerator3.default)(function* (ctx, next) {
158 ctx.status = 200;
159 yield next();
160 });
161
162 return function (_x, _x2) {
163 return _ref.apply(this, arguments);
164 };
165 })(), true);
166
167 // handle errors
168 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.ERROR)) {
169 this.add((0, _koaError2.default)(errorHandler), true);
170 }
171
172 // Redirect without trailing slash.
173 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.TRAILING_SLASH)) {
174 this.add((() => {
175 var _ref2 = (0, _asyncToGenerator3.default)(function* (ctx, next) {
176 if (base === ctx.path) {
177 ctx.redirect(`${ctx.path}/${ctx.search}`);
178 } else {
179 yield next();
180 }
181 });
182
183 return function (_x3, _x4) {
184 return _ref2.apply(this, arguments);
185 };
186 })());
187 }
188
189 if (ssr) {
190 /**
191 * Add static files middleware only in prod mode,
192 * because we already have webpack-dev-middleware in dev mode.
193 * Don't need this middleware when CDN being used to serve static files.
194 */
195 if (this.isProd && !(0, _path2.isFromCDN)(publicPath)) {
196
197 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.STATIC)) {
198 // serve /static
199 this.add(mount(_path.posix.join(publicPath, _constants.ASSETS_DIRNAME_IN_DIST), koaStatic((0, _path.join)(this.cwd, _constants.ASSETS_DIRNAME_IN_DIST))));
200 }
201
202 // serve sw-register.js & sw.js
203 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.SERVICE_WORKER) && serviceWorker && serviceWorker.swDest) {
204 let swFiles = [(0, _path.basename)(serviceWorker.swDest), 'sw-register.js'].map(f => _path.posix.join(publicPath, f));
205 this.add((() => {
206 var _ref3 = (0, _asyncToGenerator3.default)(function* (ctx, next) {
207 let done = false;
208 if (swFiles.includes(ctx.path)) {
209 // Don't cache service-worker.js & sw-register.js.
210 ctx.set('Cache-Control', 'private, no-cache, no-store');
211 done = yield send(ctx, ctx.path.substring(publicPath.length), {
212 root: _this.cwd
213 });
214 }
215 if (!done) {
216 yield next();
217 }
218 });
219
220 return function (_x5, _x6) {
221 return _ref3.apply(this, arguments);
222 };
223 })());
224 }
225 }
226
227 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.SSR)) {
228 this.add(c2k((0, _ssr2.default)(this.core)));
229 }
230 }
231
232 return composeKoa(this.internalMiddlewares);
233 }
234
235 /**
236 * compose middlewares into a chain.
237 *
238 * @param {Array<Function>|Function} selectedMiddlewares middlewares selected by user
239 * @return {Function} expressMiddleware
240 */
241 express(selectedMiddlewares = ALL_MIDDLEWARES) {
242 if (!Array.isArray(selectedMiddlewares)) {
243 selectedMiddlewares = [selectedMiddlewares];
244 }
245
246 let expressRouter = _express.Router;
247 let { router: { base }, build: { ssr, publicPath }, serviceWorker, errorHandler } = this.config;
248 base = (0, _path2.removeTrailingSlash)(base || '/');
249
250 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.COMPRESSION)) {
251 // gzip compression
252 this.add((0, _compression2.default)());
253 }
254
255 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.FAVICON)) {
256 // serve favicon
257 let faviconPath = _path.posix.join(this.cwd, _constants.ASSETS_DIRNAME_IN_DIST, 'img/icons/favicon.ico');
258 this.add((0, _serveFavicon2.default)(faviconPath));
259 }
260
261 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.TRAILING_SLASH)) {
262 // Redirect without trailing slash.
263 let rootRouter = expressRouter();
264 rootRouter.get(base, (req, res, next) => {
265 let url = (0, _url.parse)(req.url);
266 if (!url.pathname.endsWith('/')) {
267 res.redirect(301, url.pathname + '/' + (url.search || ''));
268 } else {
269 next();
270 }
271 });
272 this.add(rootRouter, true);
273 }
274
275 if (ssr) {
276 /**
277 * Add static files middleware only in prod mode,
278 * because we already have webpack-dev-middleware in dev mode.
279 * Don't need this middleware when CDN being used to serve static files.
280 */
281 if (this.isProd && !(0, _path2.isFromCDN)(publicPath)) {
282 // Serve /static.
283 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.STATIC)) {
284 let staticRouter = expressRouter();
285 staticRouter.get(_path.posix.join(publicPath, _constants.ASSETS_DIRNAME_IN_DIST, '*'), (0, _static2.default)(publicPath));
286 this.add(staticRouter);
287 // Don't use etag or cache-control.
288 this.add((0, _serveStatic2.default)(this.cwd, {
289 cacheControl: false,
290 etag: false
291 }));
292 }
293
294 // Serve sw-register.js & sw.js.
295 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.SERVICE_WORKER)) {
296 if (serviceWorker && serviceWorker.swDest) {
297 let swFiles = [(0, _path.basename)(serviceWorker.swDest), 'sw-register.js'].map(f => _path.posix.join(publicPath, f));
298 let swRouter = expressRouter();
299 swRouter.get(swFiles, (0, _static2.default)(publicPath));
300 this.add(swRouter);
301 // Use cache-control but not etag.
302 this.add((0, _serveStatic2.default)(this.cwd, {
303 etag: false
304 }));
305 }
306 }
307 }
308
309 // SSR middleware.
310 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.SSR)) {
311 this.add((0, _ssr2.default)(this.core));
312 }
313 }
314
315 // Handle errors.
316 if (selectedMiddlewares.includes(INTERNAL_MIDDLEWARE.ERROR)) {
317 this.add((0, _expressError2.default)(errorHandler));
318 }
319
320 return (0, _composeMiddleware.compose)(this.internalMiddlewares);
321 }
322}
323exports.default = MiddlewareComposer;
324module.exports = exports['default'];
\No newline at end of file