webpack-dev-server 2.2.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/bin/webpack-dev-server.js +21 -32
- package/client/index.bundle.js +1 -3
- package/client/index.js +29 -2
- package/client/live.bundle.js +3 -6
- package/client/overlay.js +126 -0
- package/client/sockjs.bundle.js +1 -2
- package/lib/OptionsValidationError.js +107 -101
- package/lib/Server.js +74 -54
- package/lib/optionsSchema.json +27 -1
- package/lib/util/addDevServerEntrypoints.js +26 -0
- package/lib/util/createDomain.js +13 -0
- package/package.json +12 -8
|
@@ -2,69 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const optionsSchema = require("./optionsSchema.json");
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Error.call(this);
|
|
7
|
-
Error.captureStackTrace(this, OptionsValidationError);
|
|
8
|
-
this.name = "WebpackDevServerOptionsValidationError";
|
|
9
|
-
this.message = `${"Invalid configuration object. " +
|
|
10
|
-
"webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n"}${
|
|
11
|
-
validationErrors.map(function(err) {
|
|
12
|
-
return ` - ${indent(OptionsValidationError.formatValidationError(err), " ", false)}`;
|
|
13
|
-
}).join("\n")}`;
|
|
14
|
-
this.validationErrors = validationErrors;
|
|
15
|
-
}
|
|
16
|
-
module.exports = OptionsValidationError;
|
|
17
|
-
|
|
18
|
-
OptionsValidationError.prototype = Object.create(Error.prototype);
|
|
19
|
-
OptionsValidationError.prototype.constructor = OptionsValidationError;
|
|
20
|
-
|
|
21
|
-
OptionsValidationError.formatValidationError = function formatValidationError(err) {
|
|
22
|
-
const dataPath = `configuration${err.dataPath}`;
|
|
23
|
-
switch(err.keyword) {
|
|
24
|
-
case "additionalProperties":
|
|
25
|
-
return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${
|
|
26
|
-
getSchemaPartText(err.parentSchema)}`;
|
|
27
|
-
case "oneOf":
|
|
28
|
-
case "anyOf":
|
|
29
|
-
case "enum":
|
|
30
|
-
return `${dataPath} should be one of these:\n${
|
|
31
|
-
getSchemaPartText(err.parentSchema)}`;
|
|
32
|
-
case "allOf":
|
|
33
|
-
return `${dataPath} should be:\n${
|
|
34
|
-
getSchemaPartText(err.parentSchema)}`;
|
|
35
|
-
case "type":
|
|
36
|
-
switch(err.params.type) {
|
|
37
|
-
case "object":
|
|
38
|
-
return `${dataPath} should be an object.`;
|
|
39
|
-
case "array":
|
|
40
|
-
return `${dataPath} should be an array.`;
|
|
41
|
-
case "string":
|
|
42
|
-
return `${dataPath} should be a string.`;
|
|
43
|
-
case "boolean":
|
|
44
|
-
return `${dataPath} should be a boolean.`;
|
|
45
|
-
case "number":
|
|
46
|
-
return `${dataPath} should be a number.`;
|
|
47
|
-
}
|
|
48
|
-
return `${dataPath} should be ${err.params.type}:\n${
|
|
49
|
-
getSchemaPartText(err.parentSchema)}`;
|
|
50
|
-
case "instanceof":
|
|
51
|
-
return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`;
|
|
52
|
-
case "required": // eslint-disable-line no-case-declarations
|
|
53
|
-
const missingProperty = err.params.missingProperty.replace(/^\./, "");
|
|
54
|
-
return `${dataPath} misses the property '${missingProperty}'.\n${
|
|
55
|
-
getSchemaPartText(err.parentSchema, ["properties", missingProperty])}`;
|
|
56
|
-
case "minLength":
|
|
57
|
-
if(err.params.limit === 1)
|
|
58
|
-
return `${dataPath} should not be empty.`;
|
|
59
|
-
else
|
|
60
|
-
return `${dataPath} ${err.message}`;
|
|
61
|
-
default:
|
|
62
|
-
return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${
|
|
63
|
-
getSchemaPartText(err.parentSchema)}`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function getSchemaPart(path, parents, additionalPath) {
|
|
5
|
+
const getSchemaPart = (path, parents, additionalPath) => {
|
|
68
6
|
parents = parents || 0;
|
|
69
7
|
path = path.split("/");
|
|
70
8
|
path = path.slice(0, path.length - parents);
|
|
@@ -79,9 +17,9 @@ function getSchemaPart(path, parents, additionalPath) {
|
|
|
79
17
|
schemaPart = inner;
|
|
80
18
|
}
|
|
81
19
|
return schemaPart;
|
|
82
|
-
}
|
|
20
|
+
};
|
|
83
21
|
|
|
84
|
-
|
|
22
|
+
const getSchemaPartText = (schemaPart, additionalPath) => {
|
|
85
23
|
if(additionalPath) {
|
|
86
24
|
for(let i = 0; i < additionalPath.length; i++) {
|
|
87
25
|
const inner = schemaPart[additionalPath[i]];
|
|
@@ -94,28 +32,56 @@ function getSchemaPartText(schemaPart, additionalPath) {
|
|
|
94
32
|
if(schemaPart.description)
|
|
95
33
|
schemaText += `\n${schemaPart.description}`;
|
|
96
34
|
return schemaText;
|
|
97
|
-
}
|
|
35
|
+
};
|
|
98
36
|
|
|
99
|
-
|
|
100
|
-
|
|
37
|
+
const indent = (str, prefix, firstLine) => {
|
|
38
|
+
if(firstLine) {
|
|
39
|
+
return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
|
|
40
|
+
} else {
|
|
41
|
+
return str.replace(/\n(?!$)/g, `\n${prefix}`);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
class OptionsValidationError extends Error {
|
|
46
|
+
|
|
47
|
+
constructor(validationErrors) {
|
|
48
|
+
super();
|
|
49
|
+
|
|
50
|
+
if(Error.hasOwnProperty("captureStackTrace")) {
|
|
51
|
+
Error.captureStackTrace(this, this.constructor);
|
|
52
|
+
}
|
|
53
|
+
this.name = "WebpackDevServerOptionsValidationError";
|
|
101
54
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
55
|
+
this.message = "Invalid configuration object. " +
|
|
56
|
+
"webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n" +
|
|
57
|
+
validationErrors.map(err => " - " + indent(OptionsValidationError.formatValidationError(err), " ", false)).join("\n");
|
|
58
|
+
this.validationErrors = validationErrors;
|
|
106
59
|
}
|
|
107
|
-
|
|
108
|
-
|
|
60
|
+
|
|
61
|
+
static formatSchema(schema, prevSchemas) {
|
|
62
|
+
prevSchemas = prevSchemas || [];
|
|
63
|
+
|
|
64
|
+
const formatInnerSchema = (innerSchema, addSelf) => {
|
|
65
|
+
if(!addSelf) return OptionsValidationError.formatSchema(innerSchema, prevSchemas);
|
|
66
|
+
if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
|
|
67
|
+
return OptionsValidationError.formatSchema(innerSchema, prevSchemas.concat(schema));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if(schema.type === "string") {
|
|
71
|
+
if(schema.minLength === 1)
|
|
72
|
+
return "non-empty string";
|
|
73
|
+
else if(schema.minLength > 1)
|
|
74
|
+
return `string (min length ${schema.minLength})`;
|
|
109
75
|
return "string";
|
|
110
|
-
|
|
76
|
+
} else if(schema.type === "boolean") {
|
|
111
77
|
return "boolean";
|
|
112
|
-
|
|
78
|
+
} else if(schema.type === "number") {
|
|
113
79
|
return "number";
|
|
114
|
-
|
|
80
|
+
} else if(schema.type === "object") {
|
|
115
81
|
if(schema.properties) {
|
|
116
82
|
const required = schema.required || [];
|
|
117
|
-
return `object { ${Object.keys(schema.properties).map(
|
|
118
|
-
if(required.indexOf(property) < 0) return
|
|
83
|
+
return `object { ${Object.keys(schema.properties).map(property => {
|
|
84
|
+
if(required.indexOf(property) < 0) return property + "?";
|
|
119
85
|
return property;
|
|
120
86
|
}).concat(schema.additionalProperties ? ["..."] : []).join(", ")} }`;
|
|
121
87
|
}
|
|
@@ -123,31 +89,71 @@ function formatSchema(schema, prevSchemas) {
|
|
|
123
89
|
return `object { <key>: ${formatInnerSchema(schema.additionalProperties)} }`;
|
|
124
90
|
}
|
|
125
91
|
return "object";
|
|
126
|
-
|
|
92
|
+
} else if(schema.type === "array") {
|
|
127
93
|
return `[${formatInnerSchema(schema.items)}]`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
switch(schema.instanceof) {
|
|
97
|
+
case "Function":
|
|
98
|
+
return "function";
|
|
99
|
+
case "RegExp":
|
|
100
|
+
return "RegExp";
|
|
101
|
+
}
|
|
102
|
+
if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
|
|
103
|
+
if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
|
|
104
|
+
if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
|
|
105
|
+
if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
|
|
106
|
+
if(schema.enum) return schema.enum.map(item => JSON.stringify(item)).join(" | ");
|
|
107
|
+
return JSON.stringify(schema, 0, 2);
|
|
128
108
|
}
|
|
129
|
-
switch(schema.instanceof) {
|
|
130
|
-
case "Function":
|
|
131
|
-
return "function";
|
|
132
|
-
case "RegExp":
|
|
133
|
-
return "RegExp";
|
|
134
|
-
}
|
|
135
|
-
if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
|
|
136
|
-
if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
|
|
137
|
-
if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
|
|
138
|
-
if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
|
|
139
|
-
if(schema.enum) return schema.enum.map(function(item) {
|
|
140
|
-
return JSON.stringify(item);
|
|
141
|
-
}).join(" | ");
|
|
142
|
-
return JSON.stringify(schema, 0, 2);
|
|
143
|
-
}
|
|
144
109
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
110
|
+
static formatValidationError(err) {
|
|
111
|
+
const dataPath = `configuration${err.dataPath}`;
|
|
112
|
+
if(err.keyword === "additionalProperties") {
|
|
113
|
+
return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
|
|
114
|
+
} else if(err.keyword === "oneOf" || err.keyword === "anyOf") {
|
|
115
|
+
if(err.children && err.children.length > 0) {
|
|
116
|
+
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}\n` +
|
|
117
|
+
`Details:\n${err.children.map(err => " * " + indent(OptionsValidationError.formatValidationError(err), " ", false)).join("\n")}`;
|
|
118
|
+
}
|
|
119
|
+
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
|
|
120
|
+
|
|
121
|
+
} else if(err.keyword === "enum") {
|
|
122
|
+
if(err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
|
|
123
|
+
return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
|
|
124
|
+
}
|
|
125
|
+
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
|
|
126
|
+
} else if(err.keyword === "allOf") {
|
|
127
|
+
return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
|
|
128
|
+
} else if(err.keyword === "type") {
|
|
129
|
+
switch(err.params.type) {
|
|
130
|
+
case "object":
|
|
131
|
+
return `${dataPath} should be an object.`;
|
|
132
|
+
case "string":
|
|
133
|
+
return `${dataPath} should be a string.`;
|
|
134
|
+
case "boolean":
|
|
135
|
+
return `${dataPath} should be a boolean.`;
|
|
136
|
+
case "number":
|
|
137
|
+
return `${dataPath} should be a number.`;
|
|
138
|
+
case "array":
|
|
139
|
+
return `${dataPath} should be an array:\n${getSchemaPartText(err.parentSchema)}`;
|
|
140
|
+
}
|
|
141
|
+
return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(err.parentSchema)}`;
|
|
142
|
+
} else if(err.keyword === "instanceof") {
|
|
143
|
+
return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`;
|
|
144
|
+
} else if(err.keyword === "required") {
|
|
145
|
+
const missingProperty = err.params.missingProperty.replace(/^\./, "");
|
|
146
|
+
return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(err.parentSchema, ["properties", missingProperty])}`;
|
|
147
|
+
} else if(err.keyword === "minLength" || err.keyword === "minItems") {
|
|
148
|
+
if(err.params.limit === 1)
|
|
149
|
+
return `${dataPath} should not be empty.`;
|
|
150
|
+
else
|
|
151
|
+
return `${dataPath} ${err.message}`;
|
|
152
|
+
} else {
|
|
153
|
+
// eslint-disable-line no-fallthrough
|
|
154
|
+
return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${getSchemaPartText(err.parentSchema)}`;
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
|
|
159
|
+
module.exports = OptionsValidationError;
|
package/lib/Server.js
CHANGED
|
@@ -34,19 +34,20 @@ function Server(compiler, options) {
|
|
|
34
34
|
this.hot = options.hot || options.hotOnly;
|
|
35
35
|
this.headers = options.headers;
|
|
36
36
|
this.clientLogLevel = options.clientLogLevel;
|
|
37
|
+
this.clientOverlay = options.overlay;
|
|
37
38
|
this.sockets = [];
|
|
38
39
|
this.contentBaseWatchers = [];
|
|
39
40
|
|
|
40
41
|
// Listening for events
|
|
41
|
-
const invalidPlugin =
|
|
42
|
+
const invalidPlugin = () => {
|
|
42
43
|
this.sockWrite(this.sockets, "invalid");
|
|
43
|
-
}
|
|
44
|
+
};
|
|
44
45
|
compiler.plugin("compile", invalidPlugin);
|
|
45
46
|
compiler.plugin("invalid", invalidPlugin);
|
|
46
|
-
compiler.plugin("done",
|
|
47
|
+
compiler.plugin("done", (stats) => {
|
|
47
48
|
this._sendStats(this.sockets, stats.toJson(clientStats));
|
|
48
49
|
this._stats = stats;
|
|
49
|
-
}
|
|
50
|
+
});
|
|
50
51
|
|
|
51
52
|
// Init express server
|
|
52
53
|
const app = this.app = new express();
|
|
@@ -54,27 +55,27 @@ function Server(compiler, options) {
|
|
|
54
55
|
// middleware for serving webpack bundle
|
|
55
56
|
this.middleware = webpackDevMiddleware(compiler, options);
|
|
56
57
|
|
|
57
|
-
app.get("/__webpack_dev_server__/live.bundle.js",
|
|
58
|
+
app.get("/__webpack_dev_server__/live.bundle.js", (req, res) => {
|
|
58
59
|
res.setHeader("Content-Type", "application/javascript");
|
|
59
60
|
fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(res);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
app.get("/__webpack_dev_server__/sockjs.bundle.js",
|
|
63
|
+
app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
|
|
63
64
|
res.setHeader("Content-Type", "application/javascript");
|
|
64
65
|
fs.createReadStream(path.join(__dirname, "..", "client", "sockjs.bundle.js")).pipe(res);
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
app.get("/webpack-dev-server.js",
|
|
68
|
+
app.get("/webpack-dev-server.js", (req, res) => {
|
|
68
69
|
res.setHeader("Content-Type", "application/javascript");
|
|
69
70
|
fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(res);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
app.get("/webpack-dev-server/*",
|
|
73
|
+
app.get("/webpack-dev-server/*", (req, res) => {
|
|
73
74
|
res.setHeader("Content-Type", "text/html");
|
|
74
75
|
fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(res);
|
|
75
76
|
});
|
|
76
77
|
|
|
77
|
-
app.get("/webpack-dev-server",
|
|
78
|
+
app.get("/webpack-dev-server", (req, res) => {
|
|
78
79
|
res.setHeader("Content-Type", "text/html");
|
|
79
80
|
/* eslint-disable quotes */
|
|
80
81
|
res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
|
|
@@ -117,7 +118,7 @@ function Server(compiler, options) {
|
|
|
117
118
|
/* eslint-enable quotes */
|
|
118
119
|
writeDirectory(options.publicPath || "/", path);
|
|
119
120
|
res.end("</body></html>");
|
|
120
|
-
}
|
|
121
|
+
});
|
|
121
122
|
|
|
122
123
|
let contentBase;
|
|
123
124
|
if(options.contentBase !== undefined) {
|
|
@@ -126,15 +127,18 @@ function Server(compiler, options) {
|
|
|
126
127
|
contentBase = process.cwd();
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// Keep track of websocket proxies for external websocket upgrade.
|
|
131
|
+
const websocketProxies = [];
|
|
132
|
+
|
|
129
133
|
const features = {
|
|
130
|
-
compress
|
|
134
|
+
compress() {
|
|
131
135
|
if(options.compress) {
|
|
132
136
|
// Enable gzip compression.
|
|
133
137
|
app.use(compress());
|
|
134
138
|
}
|
|
135
139
|
},
|
|
136
140
|
|
|
137
|
-
proxy
|
|
141
|
+
proxy() {
|
|
138
142
|
if(options.proxy) {
|
|
139
143
|
/**
|
|
140
144
|
* Assume a proxy configuration specified as:
|
|
@@ -147,7 +151,7 @@ function Server(compiler, options) {
|
|
|
147
151
|
* }
|
|
148
152
|
*/
|
|
149
153
|
if(!Array.isArray(options.proxy)) {
|
|
150
|
-
options.proxy = Object.keys(options.proxy).map(
|
|
154
|
+
options.proxy = Object.keys(options.proxy).map((context) => {
|
|
151
155
|
let proxyOptions;
|
|
152
156
|
// For backwards compatibility reasons.
|
|
153
157
|
const correctedContext = context.replace(/^\*$/, "**").replace(/\/\*$/, "");
|
|
@@ -158,7 +162,7 @@ function Server(compiler, options) {
|
|
|
158
162
|
target: options.proxy[context]
|
|
159
163
|
};
|
|
160
164
|
} else {
|
|
161
|
-
proxyOptions = options.proxy[context];
|
|
165
|
+
proxyOptions = Object.assign({}, options.proxy[context]);
|
|
162
166
|
proxyOptions.context = correctedContext;
|
|
163
167
|
}
|
|
164
168
|
proxyOptions.logLevel = proxyOptions.logLevel || "warn";
|
|
@@ -167,7 +171,7 @@ function Server(compiler, options) {
|
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
const getProxyMiddleware =
|
|
174
|
+
const getProxyMiddleware = (proxyConfig) => {
|
|
171
175
|
const context = proxyConfig.context || proxyConfig.path;
|
|
172
176
|
|
|
173
177
|
// It is possible to use the `bypass` method without a `target`.
|
|
@@ -193,7 +197,7 @@ function Server(compiler, options) {
|
|
|
193
197
|
* }
|
|
194
198
|
* ]
|
|
195
199
|
*/
|
|
196
|
-
options.proxy.forEach(
|
|
200
|
+
options.proxy.forEach((proxyConfigOrCallback) => {
|
|
197
201
|
let proxyConfig;
|
|
198
202
|
let proxyMiddleware;
|
|
199
203
|
|
|
@@ -204,8 +208,11 @@ function Server(compiler, options) {
|
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
211
|
+
if(proxyConfig.ws) {
|
|
212
|
+
websocketProxies.push(proxyMiddleware);
|
|
213
|
+
}
|
|
207
214
|
|
|
208
|
-
app.use(
|
|
215
|
+
app.use((req, res, next) => {
|
|
209
216
|
if(typeof proxyConfigOrCallback === "function") {
|
|
210
217
|
const newProxyConfig = proxyConfigOrCallback();
|
|
211
218
|
if(newProxyConfig !== proxyConfig) {
|
|
@@ -229,7 +236,7 @@ function Server(compiler, options) {
|
|
|
229
236
|
}
|
|
230
237
|
},
|
|
231
238
|
|
|
232
|
-
historyApiFallback
|
|
239
|
+
historyApiFallback() {
|
|
233
240
|
if(options.historyApiFallback) {
|
|
234
241
|
// Fall back to /index.html if nothing else matches.
|
|
235
242
|
app.use(
|
|
@@ -238,16 +245,16 @@ function Server(compiler, options) {
|
|
|
238
245
|
}
|
|
239
246
|
},
|
|
240
247
|
|
|
241
|
-
contentBaseFiles
|
|
248
|
+
contentBaseFiles() {
|
|
242
249
|
if(Array.isArray(contentBase)) {
|
|
243
|
-
contentBase.forEach(
|
|
250
|
+
contentBase.forEach((item) => {
|
|
244
251
|
app.get("*", express.static(item));
|
|
245
252
|
});
|
|
246
253
|
} else if(/^(https?:)?\/\//.test(contentBase)) {
|
|
247
254
|
console.log("Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
|
|
248
255
|
console.log('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
|
|
249
256
|
// Redirect every request to contentBase
|
|
250
|
-
app.get("*",
|
|
257
|
+
app.get("*", (req, res) => {
|
|
251
258
|
res.writeHead(302, {
|
|
252
259
|
"Location": contentBase + req.path + (req._parsedUrl.search || "")
|
|
253
260
|
});
|
|
@@ -257,7 +264,7 @@ function Server(compiler, options) {
|
|
|
257
264
|
console.log("Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
|
|
258
265
|
console.log('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
|
|
259
266
|
// Redirect every request to the port contentBase
|
|
260
|
-
app.get("*",
|
|
267
|
+
app.get("*", (req, res) => {
|
|
261
268
|
res.writeHead(302, {
|
|
262
269
|
"Location": `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ""}`
|
|
263
270
|
});
|
|
@@ -269,9 +276,9 @@ function Server(compiler, options) {
|
|
|
269
276
|
}
|
|
270
277
|
},
|
|
271
278
|
|
|
272
|
-
contentBaseIndex
|
|
279
|
+
contentBaseIndex() {
|
|
273
280
|
if(Array.isArray(contentBase)) {
|
|
274
|
-
contentBase.forEach(
|
|
281
|
+
contentBase.forEach((item) => {
|
|
275
282
|
app.get("*", serveIndex(item));
|
|
276
283
|
});
|
|
277
284
|
} else if(!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== "number") {
|
|
@@ -279,35 +286,35 @@ function Server(compiler, options) {
|
|
|
279
286
|
}
|
|
280
287
|
},
|
|
281
288
|
|
|
282
|
-
watchContentBase:
|
|
289
|
+
watchContentBase: () => {
|
|
283
290
|
if(/^(https?:)?\/\//.test(contentBase) || typeof contentBase === "number") {
|
|
284
291
|
throw new Error("Watching remote files is not supported.");
|
|
285
292
|
} else if(Array.isArray(contentBase)) {
|
|
286
|
-
contentBase.forEach(
|
|
293
|
+
contentBase.forEach((item) => {
|
|
287
294
|
this._watch(item);
|
|
288
|
-
}
|
|
295
|
+
});
|
|
289
296
|
} else {
|
|
290
297
|
this._watch(contentBase);
|
|
291
298
|
}
|
|
292
|
-
}
|
|
299
|
+
},
|
|
293
300
|
|
|
294
|
-
middleware:
|
|
301
|
+
middleware: () => {
|
|
295
302
|
// include our middleware to ensure it is able to handle '/index.html' request after redirect
|
|
296
303
|
app.use(this.middleware);
|
|
297
|
-
}
|
|
304
|
+
},
|
|
298
305
|
|
|
299
|
-
headers:
|
|
306
|
+
headers: () => {
|
|
300
307
|
app.all("*", this.setContentHeaders.bind(this));
|
|
301
|
-
}
|
|
308
|
+
},
|
|
302
309
|
|
|
303
|
-
magicHtml:
|
|
310
|
+
magicHtml: () => {
|
|
304
311
|
app.get("*", this.serveMagicHtml.bind(this));
|
|
305
|
-
}
|
|
312
|
+
},
|
|
306
313
|
|
|
307
|
-
setup:
|
|
314
|
+
setup: () => {
|
|
308
315
|
if(typeof options.setup === "function")
|
|
309
316
|
options.setup(app, this);
|
|
310
|
-
}
|
|
317
|
+
}
|
|
311
318
|
};
|
|
312
319
|
|
|
313
320
|
const defaultFeatures = ["setup", "headers", "middleware"];
|
|
@@ -317,8 +324,11 @@ function Server(compiler, options) {
|
|
|
317
324
|
defaultFeatures.push("contentBaseFiles");
|
|
318
325
|
if(options.watchContentBase)
|
|
319
326
|
defaultFeatures.push("watchContentBase");
|
|
320
|
-
if(options.historyApiFallback)
|
|
321
|
-
defaultFeatures.push("historyApiFallback", "middleware"
|
|
327
|
+
if(options.historyApiFallback) {
|
|
328
|
+
defaultFeatures.push("historyApiFallback", "middleware");
|
|
329
|
+
if(contentBase !== false)
|
|
330
|
+
defaultFeatures.push("contentBaseFiles");
|
|
331
|
+
}
|
|
322
332
|
defaultFeatures.push("magicHtml");
|
|
323
333
|
if(contentBase !== false)
|
|
324
334
|
defaultFeatures.push("contentBaseIndex");
|
|
@@ -326,9 +336,9 @@ function Server(compiler, options) {
|
|
|
326
336
|
if(options.compress)
|
|
327
337
|
defaultFeatures.unshift("compress");
|
|
328
338
|
|
|
329
|
-
(options.features || defaultFeatures).forEach(
|
|
339
|
+
(options.features || defaultFeatures).forEach((feature) => {
|
|
330
340
|
features[feature]();
|
|
331
|
-
}
|
|
341
|
+
});
|
|
332
342
|
|
|
333
343
|
if(options.https) {
|
|
334
344
|
// for keep supporting CLI parameters
|
|
@@ -357,6 +367,12 @@ function Server(compiler, options) {
|
|
|
357
367
|
} else {
|
|
358
368
|
this.listeningApp = http.createServer(app);
|
|
359
369
|
}
|
|
370
|
+
|
|
371
|
+
// Proxy websockets without the initial http request
|
|
372
|
+
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
373
|
+
websocketProxies.forEach(function(wsProxy) {
|
|
374
|
+
this.listeningApp.on("upgrade", wsProxy.upgrade);
|
|
375
|
+
}, this);
|
|
360
376
|
}
|
|
361
377
|
|
|
362
378
|
Server.prototype.use = function() {
|
|
@@ -386,25 +402,28 @@ Server.prototype.listen = function() {
|
|
|
386
402
|
}
|
|
387
403
|
}
|
|
388
404
|
});
|
|
389
|
-
sockServer.on("connection",
|
|
405
|
+
sockServer.on("connection", (conn) => {
|
|
390
406
|
if(!conn) return;
|
|
391
407
|
this.sockets.push(conn);
|
|
392
408
|
|
|
393
|
-
conn.on("close",
|
|
409
|
+
conn.on("close", () => {
|
|
394
410
|
const connIndex = this.sockets.indexOf(conn);
|
|
395
411
|
if(connIndex >= 0) {
|
|
396
412
|
this.sockets.splice(connIndex, 1);
|
|
397
413
|
}
|
|
398
|
-
}
|
|
414
|
+
});
|
|
399
415
|
|
|
400
416
|
if(this.clientLogLevel)
|
|
401
417
|
this.sockWrite([conn], "log-level", this.clientLogLevel);
|
|
402
418
|
|
|
419
|
+
if(this.clientOverlay)
|
|
420
|
+
this.sockWrite([conn], "overlay", this.clientOverlay);
|
|
421
|
+
|
|
403
422
|
if(this.hot) this.sockWrite([conn], "hot");
|
|
404
423
|
|
|
405
424
|
if(!this._stats) return;
|
|
406
425
|
this._sendStats([conn], this._stats.toJson(clientStats), true);
|
|
407
|
-
}
|
|
426
|
+
});
|
|
408
427
|
|
|
409
428
|
sockServer.installHandlers(this.listeningApp, {
|
|
410
429
|
prefix: "/sockjs-node"
|
|
@@ -413,22 +432,22 @@ Server.prototype.listen = function() {
|
|
|
413
432
|
}
|
|
414
433
|
|
|
415
434
|
Server.prototype.close = function(callback) {
|
|
416
|
-
this.sockets.forEach(
|
|
435
|
+
this.sockets.forEach((sock) => {
|
|
417
436
|
sock.close();
|
|
418
437
|
});
|
|
419
438
|
this.sockets = [];
|
|
420
|
-
this.listeningApp.close(
|
|
439
|
+
this.listeningApp.close(() => {
|
|
421
440
|
this.middleware.close(callback);
|
|
422
|
-
}
|
|
441
|
+
});
|
|
423
442
|
|
|
424
|
-
this.contentBaseWatchers.forEach(
|
|
443
|
+
this.contentBaseWatchers.forEach((watcher) => {
|
|
425
444
|
watcher.close();
|
|
426
445
|
});
|
|
427
446
|
this.contentBaseWatchers = [];
|
|
428
447
|
}
|
|
429
448
|
|
|
430
449
|
Server.prototype.sockWrite = function(sockets, type, data) {
|
|
431
|
-
sockets.forEach(
|
|
450
|
+
sockets.forEach((sock) => {
|
|
432
451
|
sock.write(JSON.stringify({
|
|
433
452
|
type: type,
|
|
434
453
|
data: data
|
|
@@ -460,9 +479,7 @@ Server.prototype._sendStats = function(sockets, stats, force) {
|
|
|
460
479
|
stats &&
|
|
461
480
|
(!stats.errors || stats.errors.length === 0) &&
|
|
462
481
|
stats.assets &&
|
|
463
|
-
stats.assets.every(
|
|
464
|
-
return !asset.emitted;
|
|
465
|
-
})
|
|
482
|
+
stats.assets.every((asset) => !asset.emitted)
|
|
466
483
|
)
|
|
467
484
|
return this.sockWrite(sockets, "still-ok");
|
|
468
485
|
this.sockWrite(sockets, "hash", stats.hash);
|
|
@@ -475,9 +492,9 @@ Server.prototype._sendStats = function(sockets, stats, force) {
|
|
|
475
492
|
}
|
|
476
493
|
|
|
477
494
|
Server.prototype._watch = function(path) {
|
|
478
|
-
const watcher = chokidar.watch(path).on("change",
|
|
495
|
+
const watcher = chokidar.watch(path).on("change", () => {
|
|
479
496
|
this.sockWrite(this.sockets, "content-changed");
|
|
480
|
-
}
|
|
497
|
+
});
|
|
481
498
|
|
|
482
499
|
this.contentBaseWatchers.push(watcher);
|
|
483
500
|
}
|
|
@@ -486,4 +503,7 @@ Server.prototype.invalidate = function() {
|
|
|
486
503
|
if(this.middleware) this.middleware.invalidate();
|
|
487
504
|
}
|
|
488
505
|
|
|
506
|
+
// Export this logic, so that other implementations, like task-runners can use it
|
|
507
|
+
Server.addDevServerEntrypoints = require("./util/addDevServerEntrypoints");
|
|
508
|
+
|
|
489
509
|
module.exports = Server;
|
package/lib/optionsSchema.json
CHANGED
|
@@ -34,7 +34,14 @@
|
|
|
34
34
|
},
|
|
35
35
|
"port": {
|
|
36
36
|
"description": "The port the server listens to.",
|
|
37
|
-
"
|
|
37
|
+
"anyOf": [
|
|
38
|
+
{
|
|
39
|
+
"type": "number"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "string"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
38
45
|
},
|
|
39
46
|
"socket": {
|
|
40
47
|
"description": "The Unix socket to listen to (instead of on a host).",
|
|
@@ -60,6 +67,25 @@
|
|
|
60
67
|
"error"
|
|
61
68
|
]
|
|
62
69
|
},
|
|
70
|
+
"overlay": {
|
|
71
|
+
"description": "Shows an error overlay in browser.",
|
|
72
|
+
"anyOf": [
|
|
73
|
+
{
|
|
74
|
+
"type": "boolean"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"errors": {
|
|
80
|
+
"type": "boolean"
|
|
81
|
+
},
|
|
82
|
+
"warnings": {
|
|
83
|
+
"type": "boolean"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
63
89
|
"key": {
|
|
64
90
|
"description": "The contents of a SSL key.",
|
|
65
91
|
"anyOf": [
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const createDomain = require("./createDomain");
|
|
3
|
+
|
|
4
|
+
module.exports = function addDevServerEntrypoints(webpackOptions, devServerOptions) {
|
|
5
|
+
if(devServerOptions.inline !== false) {
|
|
6
|
+
const domain = createDomain(devServerOptions);
|
|
7
|
+
const devClient = [`${require.resolve("../../client/")}?${domain}`];
|
|
8
|
+
|
|
9
|
+
if(devServerOptions.hotOnly)
|
|
10
|
+
devClient.push("webpack/hot/only-dev-server");
|
|
11
|
+
else if(devServerOptions.hot)
|
|
12
|
+
devClient.push("webpack/hot/dev-server");
|
|
13
|
+
|
|
14
|
+
[].concat(webpackOptions).forEach((wpOpt) => {
|
|
15
|
+
if(typeof wpOpt.entry === "object" && !Array.isArray(wpOpt.entry)) {
|
|
16
|
+
Object.keys(wpOpt.entry).forEach((key) => {
|
|
17
|
+
wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]);
|
|
18
|
+
});
|
|
19
|
+
} else if(typeof wpOpt.entry === "function") {
|
|
20
|
+
wpOpt.entry = wpOpt.entry(devClient);
|
|
21
|
+
} else {
|
|
22
|
+
wpOpt.entry = devClient.concat(wpOpt.entry);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|