UNPKG

3.21 kBJavaScriptView Raw
1import Proxy from 'http-proxy';
2import http from 'http';
3import createAssetServer from './assets';
4import createRenderServer from './renderer';
5
6const options = {
7 stats: {
8 hash: false,
9 cached: false,
10 cachedAssets: false,
11 colors: true,
12 modules: false,
13 chunks: false,
14 },
15};
16
17function verify(config) {
18 if (!config) {
19 return false;
20 } else if (Array.isArray(config)) {
21 return config.some(verify);
22 } else if (typeof config === 'object') {
23 return Object.keys(config).map(key => config[key]).some(verify);
24 } else if (typeof config === 'string') {
25 return /webpack-udev-server/.test(config);
26 }
27 return false;
28}
29
30function check(config) {
31 if (!verify(config)) {
32 throw new TypeError('You must include the `webpack-udev-server` runtime.');
33 }
34}
35
36export default class Server extends http.Server {
37 constructor({ client, server }) {
38 super();
39
40 check(client);
41 check(server);
42
43 // Set some sane asset path.
44 client.output.publicPath = '/_assets';
45
46 // Create the servers.
47 this.assets = createAssetServer(client, options);
48 this.renderer = createRenderServer(server, options);
49 this.proxy = Proxy.createProxy({ ws: true });
50 this.ready = false;
51 this.state = { };
52
53 // Inform the renderer where the assets are.
54 this.renderer.assets(client.output.publicPath);
55
56 // Forward normal requests.
57 this.on('request', this.guard((req, res) => {
58 this.proxy.web(req, res, {
59 target: this.target('http', req),
60 });
61 }));
62
63 // Forward web sockets.
64 this.on('upgrade', this.guard((req, socket, head) => {
65 this.proxy.ws(req, socket, head, {
66 target: this.target('ws', req),
67 });
68 }));
69
70 // Handle proxy errors. These can occur if the proxied service crashes
71 // the connection dies with it resulting in a socket hang-up.
72 this.proxy.on('error', (error, req, res) => {
73 if (!res.headersSent) {
74 res.statusCode = 500;
75 res.setHeader('Content-Type', 'text/plain');
76 }
77 res.end(`Unable to fetch ${req.url}.`);
78 });
79 }
80
81 listen() {
82 // TODO: Handle error events from renderer/assets
83 this.renderer.once('listening', () => this.set('renderer', true));
84 this.assets.once('listening', () => this.set('assets', true));
85
86 this.assets.on('stats', stats => {
87 this.renderer.stats(stats);
88 });
89
90 this.assets.listen(0, 'localhost');
91 this.renderer.listen(0, 'localhost');
92 http.Server.prototype.listen.apply(this, arguments);
93 }
94
95 close() {
96 this.assets.close();
97 this.renderer.close();
98 http.Server.prototype.close.apply(this, arguments);
99 }
100
101 target(proto, req) {
102 if (req.url.substring(0, '/_assets'.length) === '/_assets') {
103 return `${proto}://localhost:${this.assets.address().port}`;
104 }
105 return `${proto}://localhost:${this.renderer.address().port}`;
106 }
107
108 set(type, value) {
109 this.state[type] = value;
110 this.ready = this.state.assets && this.state.renderer;
111 if (this.ready) {
112 this.emit('ready');
113 }
114 }
115
116 guard(fn) {
117 return (...args) => {
118 if (this.ready) {
119 fn(...args);
120 } else {
121 this.once('ready', () => fn(...args));
122 }
123 };
124 }
125}