1 | import Proxy from 'http-proxy';
|
2 | import http from 'http';
|
3 | import createAssetServer from './assets';
|
4 | import createRenderServer from './renderer';
|
5 |
|
6 | const options = {
|
7 | stats: {
|
8 | hash: false,
|
9 | cached: false,
|
10 | cachedAssets: false,
|
11 | colors: true,
|
12 | modules: false,
|
13 | chunks: false,
|
14 | },
|
15 | };
|
16 |
|
17 | function 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 |
|
30 | function check(config) {
|
31 | if (!verify(config)) {
|
32 | throw new TypeError('You must include the `webpack-udev-server` runtime.');
|
33 | }
|
34 | }
|
35 |
|
36 | export default class Server extends http.Server {
|
37 | constructor({ client, server }) {
|
38 | super();
|
39 |
|
40 | check(client);
|
41 | check(server);
|
42 |
|
43 |
|
44 | client.output.publicPath = '/_assets';
|
45 |
|
46 |
|
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 |
|
54 | this.renderer.assets(client.output.publicPath);
|
55 |
|
56 |
|
57 | this.on('request', this.guard((req, res) => {
|
58 | this.proxy.web(req, res, {
|
59 | target: this.target('http', req),
|
60 | });
|
61 | }));
|
62 |
|
63 |
|
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 |
|
71 |
|
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 |
|
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 | }
|