webpack-dev-service 0.9.0 → 0.10.0
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/{server/cjs → cjs/server}/index.cjs +1 -1
- package/package.json +13 -28
- package/client/cjs/client.cjs +0 -128
- package/client/cjs/events.cjs +0 -64
- package/client/cjs/hot.cjs +0 -111
- package/client/cjs/index.cjs +0 -15
- package/client/cjs/main.cjs +0 -59
- package/client/cjs/ui/overlay.cjs +0 -233
- package/client/cjs/ui/progress.cjs +0 -92
- package/client/cjs/ui/utils.cjs +0 -136
- package/client/esm/client.js +0 -126
- package/client/esm/events.js +0 -60
- package/client/esm/hot.js +0 -106
- package/client/esm/index.js +0 -10
- package/client/esm/main.js +0 -57
- package/client/esm/ui/overlay.js +0 -231
- package/client/esm/ui/progress.js +0 -90
- package/client/esm/ui/utils.js +0 -123
- package/server/cjs/compose.cjs +0 -56
- package/server/cjs/dev/Service.cjs +0 -269
- package/server/cjs/dev/index.cjs +0 -60
- package/server/cjs/dev/middleware.cjs +0 -96
- package/server/cjs/dev/utils/fs.cjs +0 -29
- package/server/cjs/dev/utils/hash.cjs +0 -46
- package/server/cjs/dev/utils/http.cjs +0 -227
- package/server/cjs/dev/utils/path.cjs +0 -46
- package/server/cjs/dev/utils/paths.cjs +0 -59
- package/server/cjs/dev/utils/ready.cjs +0 -23
- package/server/cjs/dev/utils/setupHooks.cjs +0 -106
- package/server/cjs/dev/utils/setupOutputFileSystem.cjs +0 -31
- package/server/cjs/dev/utils/setupWatching.cjs +0 -43
- package/server/cjs/dev/utils/setupWriteToDisk.cjs +0 -54
- package/server/cjs/hot/Socket.cjs +0 -174
- package/server/cjs/hot/index.cjs +0 -34
- package/server/cjs/hot/utils.cjs +0 -100
- package/server/cjs/schema.cjs +0 -127
- package/server/cjs/utils.cjs +0 -45
- package/server/esm/compose.js +0 -54
- package/server/esm/dev/Service.js +0 -260
- package/server/esm/dev/index.js +0 -58
- package/server/esm/dev/middleware.js +0 -94
- package/server/esm/dev/utils/fs.js +0 -27
- package/server/esm/dev/utils/hash.js +0 -44
- package/server/esm/dev/utils/http.js +0 -216
- package/server/esm/dev/utils/path.js +0 -42
- package/server/esm/dev/utils/paths.js +0 -57
- package/server/esm/dev/utils/ready.js +0 -21
- package/server/esm/dev/utils/setupHooks.js +0 -98
- package/server/esm/dev/utils/setupOutputFileSystem.js +0 -29
- package/server/esm/dev/utils/setupWatching.js +0 -41
- package/server/esm/dev/utils/setupWriteToDisk.js +0 -52
- package/server/esm/hot/Socket.js +0 -165
- package/server/esm/hot/index.js +0 -32
- package/server/esm/hot/utils.js +0 -93
- package/server/esm/index.js +0 -37
- package/server/esm/schema.js +0 -125
- package/server/esm/utils.js +0 -37
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import createEtag from 'etag';
|
|
11
|
-
import destroy from 'destroy';
|
|
12
|
-
import { PassThrough } from 'stream';
|
|
13
|
-
import { isFunction } from '../utils.js';
|
|
14
|
-
import { stat } from './utils/fs.js';
|
|
15
|
-
import { resolve, extname, join } from 'path';
|
|
16
|
-
import { unixify, isOutRoot, hasTrailingSlash } from './utils/path.js';
|
|
17
|
-
import { isConditionalGET, isPreconditionFailure, parseRanges } from './utils/http.js';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @module Service
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* @class Service
|
|
24
|
-
*/
|
|
25
|
-
class Service {
|
|
26
|
-
/**
|
|
27
|
-
* @constructor
|
|
28
|
-
* @description Create file service.
|
|
29
|
-
* @param root File service root.
|
|
30
|
-
* @param options File service options.
|
|
31
|
-
*/
|
|
32
|
-
constructor(root, options) {
|
|
33
|
-
this.options = options;
|
|
34
|
-
this.root = unixify(resolve(root));
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* @private
|
|
38
|
-
* @method isIgnore
|
|
39
|
-
* @description Check if path is ignore.
|
|
40
|
-
* @param path File path.
|
|
41
|
-
*/
|
|
42
|
-
isIgnore(path) {
|
|
43
|
-
const { ignore } = this.options;
|
|
44
|
-
return (isFunction(ignore) ? ignore(path) : false) === true;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* @private
|
|
48
|
-
* @method setupHeaders
|
|
49
|
-
* @description Setup headers
|
|
50
|
-
* @param context Koa context
|
|
51
|
-
* @param path File path
|
|
52
|
-
* @param stats File stats
|
|
53
|
-
*/
|
|
54
|
-
setupHeaders(context, path, stats) {
|
|
55
|
-
const { options } = this;
|
|
56
|
-
const { headers, etag } = options;
|
|
57
|
-
// Set status.
|
|
58
|
-
context.status = 200;
|
|
59
|
-
// Set Content-Type.
|
|
60
|
-
context.type = extname(path);
|
|
61
|
-
// Accept-Ranges.
|
|
62
|
-
if (options.acceptRanges === false) {
|
|
63
|
-
// Set Accept-Ranges to none tell client not support.
|
|
64
|
-
context.set('Accept-Ranges', 'none');
|
|
65
|
-
} else {
|
|
66
|
-
// Set Accept-Ranges.
|
|
67
|
-
context.set('Accept-Ranges', 'bytes');
|
|
68
|
-
}
|
|
69
|
-
// ETag.
|
|
70
|
-
if (etag === false) {
|
|
71
|
-
// Remove ETag.
|
|
72
|
-
context.remove('ETag');
|
|
73
|
-
} else {
|
|
74
|
-
// Set ETag.
|
|
75
|
-
context.set('ETag', createEtag(stats));
|
|
76
|
-
}
|
|
77
|
-
// Last-Modified.
|
|
78
|
-
if (options.lastModified === false) {
|
|
79
|
-
// Remove Last-Modified.
|
|
80
|
-
context.remove('Last-Modified');
|
|
81
|
-
} else {
|
|
82
|
-
// Set mtime utc string.
|
|
83
|
-
context.set('Last-Modified', stats.mtime.toUTCString());
|
|
84
|
-
}
|
|
85
|
-
// Set headers.
|
|
86
|
-
if (headers) {
|
|
87
|
-
if (isFunction(headers)) {
|
|
88
|
-
const fields = headers(path, stats);
|
|
89
|
-
if (fields) {
|
|
90
|
-
context.set(fields);
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
context.set(headers);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* @private
|
|
99
|
-
* @method write
|
|
100
|
-
* @description Write file to stream.
|
|
101
|
-
* @param stream Destination stream.
|
|
102
|
-
* @param path The file path to read.
|
|
103
|
-
* @param range The range to read.
|
|
104
|
-
* @param end Is destory destination stream after read complete.
|
|
105
|
-
*/
|
|
106
|
-
write(stream, path, range, end) {
|
|
107
|
-
const { fs } = this.options;
|
|
108
|
-
return new Promise(resolve => {
|
|
109
|
-
// Range prefix and suffix.
|
|
110
|
-
const { prefix, suffix } = range;
|
|
111
|
-
// Create file stream reader.
|
|
112
|
-
const reader = fs.createReadStream(path, range);
|
|
113
|
-
// File read stream open.
|
|
114
|
-
if (prefix) {
|
|
115
|
-
reader.once('open', () => {
|
|
116
|
-
// Write prefix boundary.
|
|
117
|
-
stream.write(prefix);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
// File read stream end.
|
|
121
|
-
if (suffix) {
|
|
122
|
-
reader.once('end', () => {
|
|
123
|
-
// Push suffix boundary.
|
|
124
|
-
stream.write(suffix);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
// File read stream error.
|
|
128
|
-
reader.once('error', () => {
|
|
129
|
-
// End stream.
|
|
130
|
-
stream.end();
|
|
131
|
-
// Unpipe.
|
|
132
|
-
reader.unpipe();
|
|
133
|
-
// Destroy.
|
|
134
|
-
destroy(reader);
|
|
135
|
-
// Resolve.
|
|
136
|
-
resolve(false);
|
|
137
|
-
});
|
|
138
|
-
// File read stream close.
|
|
139
|
-
reader.once('close', () => {
|
|
140
|
-
// Unpipe.
|
|
141
|
-
reader.unpipe();
|
|
142
|
-
// Destroy.
|
|
143
|
-
destroy(reader);
|
|
144
|
-
// Resolve.
|
|
145
|
-
resolve(true);
|
|
146
|
-
});
|
|
147
|
-
// Write data to buffer.
|
|
148
|
-
reader.pipe(stream, { end });
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* @private
|
|
153
|
-
* @method send
|
|
154
|
-
* @description Send file.
|
|
155
|
-
* @param context Koa context.
|
|
156
|
-
* @param path File path.
|
|
157
|
-
* @param ranges Read ranges.
|
|
158
|
-
*/
|
|
159
|
-
async send(context, path, ranges) {
|
|
160
|
-
// Set stream body, highWaterMark 64kb.
|
|
161
|
-
const stream = new PassThrough({
|
|
162
|
-
highWaterMark: 65536
|
|
163
|
-
});
|
|
164
|
-
// Set response body.
|
|
165
|
-
context.body = stream;
|
|
166
|
-
// Ranges length.
|
|
167
|
-
let { length } = ranges;
|
|
168
|
-
// Write ranges to stream.
|
|
169
|
-
for (const range of ranges) {
|
|
170
|
-
// Write range.
|
|
171
|
-
const passed = await this.write(stream, path, range, --length === 0);
|
|
172
|
-
// If not passed, break.
|
|
173
|
-
if (!passed) {
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* @public
|
|
180
|
-
* @method response
|
|
181
|
-
* @description Response to koa context.
|
|
182
|
-
* @param context Koa context.
|
|
183
|
-
*/
|
|
184
|
-
async response(context) {
|
|
185
|
-
const { root } = this;
|
|
186
|
-
// Only support GET and HEAD (405).
|
|
187
|
-
if (context.method !== 'GET' && context.method !== 'HEAD') {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
// Get path of file.
|
|
191
|
-
const path = unixify(join(root, context.path));
|
|
192
|
-
// Malicious path (403).
|
|
193
|
-
if (isOutRoot(path, root)) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
// Is ignore path or file (403).
|
|
197
|
-
if (this.isIgnore(path)) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
// File stats.
|
|
201
|
-
const stats = await stat(this.options.fs, path);
|
|
202
|
-
// Check file stats.
|
|
203
|
-
if (
|
|
204
|
-
// File not exist (404 | 500).
|
|
205
|
-
stats == null ||
|
|
206
|
-
// Is directory (403).
|
|
207
|
-
stats.isDirectory() ||
|
|
208
|
-
// Not a directory but has trailing slash (404).
|
|
209
|
-
hasTrailingSlash(path)
|
|
210
|
-
) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
// Setup headers.
|
|
214
|
-
this.setupHeaders(context, path, stats);
|
|
215
|
-
// Conditional get support.
|
|
216
|
-
if (isConditionalGET(context)) {
|
|
217
|
-
// Request precondition failure.
|
|
218
|
-
if (isPreconditionFailure(context)) {
|
|
219
|
-
return context.throw(412);
|
|
220
|
-
}
|
|
221
|
-
// Request fresh (304).
|
|
222
|
-
if (context.fresh) {
|
|
223
|
-
// Set status.
|
|
224
|
-
context.status = 304;
|
|
225
|
-
// Set body null.
|
|
226
|
-
context.body = null;
|
|
227
|
-
// File found.
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Head request.
|
|
232
|
-
if (context.method === 'HEAD') {
|
|
233
|
-
// Set Content-Length.
|
|
234
|
-
context.length = stats.size;
|
|
235
|
-
// Set body null
|
|
236
|
-
context.body = null;
|
|
237
|
-
// File found.
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
// Parsed ranges.
|
|
241
|
-
const ranges = parseRanges(context, stats);
|
|
242
|
-
// 416
|
|
243
|
-
if (ranges === -1) {
|
|
244
|
-
// Set Content-Range.
|
|
245
|
-
context.set('Content-Range', `bytes */${stats.size}`);
|
|
246
|
-
// Unsatisfiable 416.
|
|
247
|
-
return context.throw(416);
|
|
248
|
-
}
|
|
249
|
-
// 400.
|
|
250
|
-
if (ranges === -2) {
|
|
251
|
-
return context.throw(400);
|
|
252
|
-
}
|
|
253
|
-
// Send file.
|
|
254
|
-
this.send(context, path, ranges);
|
|
255
|
-
// File found.
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export { Service };
|
package/server/esm/dev/index.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { ready } from './utils/ready.js';
|
|
11
|
-
import { middleware } from './middleware.js';
|
|
12
|
-
import { PLUGIN_NAME } from '../utils.js';
|
|
13
|
-
import { setupHooks } from './utils/setupHooks.js';
|
|
14
|
-
import { setupWatching } from './utils/setupWatching.js';
|
|
15
|
-
import { setupWriteToDisk } from './utils/setupWriteToDisk.js';
|
|
16
|
-
import { setupOutputFileSystem } from './utils/setupOutputFileSystem.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @module index
|
|
20
|
-
*/
|
|
21
|
-
function setup(compiler, options) {
|
|
22
|
-
const context = {
|
|
23
|
-
options,
|
|
24
|
-
compiler,
|
|
25
|
-
stats: null,
|
|
26
|
-
callbacks: [],
|
|
27
|
-
logger: compiler.getInfrastructureLogger(PLUGIN_NAME)
|
|
28
|
-
};
|
|
29
|
-
setupHooks(context);
|
|
30
|
-
if (options.writeToDisk) {
|
|
31
|
-
setupWriteToDisk(context);
|
|
32
|
-
}
|
|
33
|
-
setupOutputFileSystem(context);
|
|
34
|
-
setupWatching(context);
|
|
35
|
-
return context;
|
|
36
|
-
}
|
|
37
|
-
function dev(compiler, options) {
|
|
38
|
-
const context = setup(compiler, options);
|
|
39
|
-
return Object.assign(middleware(context), {
|
|
40
|
-
get state() {
|
|
41
|
-
return !!context.stats;
|
|
42
|
-
},
|
|
43
|
-
get logger() {
|
|
44
|
-
return context.logger;
|
|
45
|
-
},
|
|
46
|
-
ready(callback) {
|
|
47
|
-
ready(context, callback);
|
|
48
|
-
},
|
|
49
|
-
close(callback) {
|
|
50
|
-
context.watching.close(callback);
|
|
51
|
-
},
|
|
52
|
-
invalidate(callback) {
|
|
53
|
-
context.watching.invalidate(callback);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export { dev };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Service } from './Service.js';
|
|
11
|
-
import { ready } from './utils/ready.js';
|
|
12
|
-
import { decodeURI } from './utils/http.js';
|
|
13
|
-
import { getPaths } from './utils/paths.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @module middleware
|
|
17
|
-
*/
|
|
18
|
-
function getFileServices(context, stats) {
|
|
19
|
-
const cached = context.services;
|
|
20
|
-
// Cache hit.
|
|
21
|
-
if (cached) {
|
|
22
|
-
return cached;
|
|
23
|
-
}
|
|
24
|
-
const paths = getPaths(stats);
|
|
25
|
-
const { fs, options } = context;
|
|
26
|
-
const services = [];
|
|
27
|
-
const { etag, ignore, headers, acceptRanges, lastModified } = options;
|
|
28
|
-
// Get the file services.
|
|
29
|
-
for (const [outputPath, publicPath] of paths) {
|
|
30
|
-
services.push([
|
|
31
|
-
publicPath,
|
|
32
|
-
new Service(outputPath, {
|
|
33
|
-
fs,
|
|
34
|
-
etag,
|
|
35
|
-
ignore,
|
|
36
|
-
headers,
|
|
37
|
-
acceptRanges,
|
|
38
|
-
lastModified
|
|
39
|
-
})
|
|
40
|
-
]);
|
|
41
|
-
}
|
|
42
|
-
// Cache services.
|
|
43
|
-
context.services = services;
|
|
44
|
-
// Return services.
|
|
45
|
-
return services;
|
|
46
|
-
}
|
|
47
|
-
function getFileServicesAsync(context, path) {
|
|
48
|
-
return new Promise(resolve => {
|
|
49
|
-
const { stats } = context;
|
|
50
|
-
// If stats exists, resolve immediately.
|
|
51
|
-
if (stats) {
|
|
52
|
-
resolve(getFileServices(context, stats));
|
|
53
|
-
} else {
|
|
54
|
-
// Log waiting info.
|
|
55
|
-
context.logger.info(`wait until bundle finished: ${path}`);
|
|
56
|
-
// Otherwise, wait until bundle finished.
|
|
57
|
-
ready(context, stats => {
|
|
58
|
-
resolve(getFileServices(context, stats));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
function middleware(context) {
|
|
64
|
-
// Middleware.
|
|
65
|
-
return async (ctx, next) => {
|
|
66
|
-
const path = decodeURI(ctx.path);
|
|
67
|
-
// Path -1 or null byte(s).
|
|
68
|
-
if (path === -1 || path.includes('\0')) {
|
|
69
|
-
return ctx.throw(400);
|
|
70
|
-
}
|
|
71
|
-
// Is path respond.
|
|
72
|
-
let respond = false;
|
|
73
|
-
// Get the file services.
|
|
74
|
-
const services = await getFileServicesAsync(context, path);
|
|
75
|
-
// Try to respond.
|
|
76
|
-
for (const [publicPath, service] of services) {
|
|
77
|
-
if (path.startsWith(publicPath)) {
|
|
78
|
-
ctx.path = path.slice(publicPath.length);
|
|
79
|
-
respond = await service.response(ctx);
|
|
80
|
-
if (respond) {
|
|
81
|
-
return;
|
|
82
|
-
} else {
|
|
83
|
-
ctx.path = path;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// Not respond.
|
|
88
|
-
if (!respond) {
|
|
89
|
-
await next();
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export { middleware };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @module fs
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* @function stat
|
|
15
|
-
* @description Get file stats.
|
|
16
|
-
* @param fs The file system to used.
|
|
17
|
-
* @param path The file path.
|
|
18
|
-
*/
|
|
19
|
-
function stat(fs, path) {
|
|
20
|
-
return new Promise(resolve => {
|
|
21
|
-
fs.stat(path, (error, stats) => {
|
|
22
|
-
resolve(error ? null : stats);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export { stat };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { getRandomValues } from 'crypto';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @module hash
|
|
14
|
-
*/
|
|
15
|
-
// prettier-ignore
|
|
16
|
-
const CHARS = [
|
|
17
|
-
// 0-9
|
|
18
|
-
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
19
|
-
// A-M
|
|
20
|
-
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
21
|
-
// N-Z
|
|
22
|
-
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
23
|
-
// a-m
|
|
24
|
-
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
25
|
-
// n-z
|
|
26
|
-
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
|
27
|
-
];
|
|
28
|
-
/**
|
|
29
|
-
* @function generate
|
|
30
|
-
* @description Generate a hash.
|
|
31
|
-
* @param length The length of hash.
|
|
32
|
-
*/
|
|
33
|
-
function generate(length = 32) {
|
|
34
|
-
let hash = '';
|
|
35
|
-
const randomValues = getRandomValues(new Uint8Array(length));
|
|
36
|
-
// Create hash.
|
|
37
|
-
for (const value of randomValues) {
|
|
38
|
-
hash += CHARS[value % 62];
|
|
39
|
-
}
|
|
40
|
-
// Return hash.
|
|
41
|
-
return hash;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { generate };
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @package webpack-dev-service
|
|
3
|
-
* @license MIT
|
|
4
|
-
* @version 0.9.0
|
|
5
|
-
* @author nuintun <nuintun@qq.com>
|
|
6
|
-
* @description A koa 2 middleware for webpack development and hot reloading.
|
|
7
|
-
* @see https://github.com/nuintun/webpack-dev-service#readme
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { generate } from './hash.js';
|
|
11
|
-
import parseRange from 'range-parser';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @module http
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* @function isETag
|
|
18
|
-
* @description Check if etag is valid.
|
|
19
|
-
* @param value The value to check.
|
|
20
|
-
*/
|
|
21
|
-
function isETag(value) {
|
|
22
|
-
return /^(?:W\/)?"[\s\S]+"$/.test(value);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* @function parseTokens
|
|
26
|
-
* @description Parse HTTP tokens.
|
|
27
|
-
* @param value The tokens value string.
|
|
28
|
-
*/
|
|
29
|
-
function parseTokens(value) {
|
|
30
|
-
let end = 0;
|
|
31
|
-
let start = 0;
|
|
32
|
-
let tokens = [];
|
|
33
|
-
// Gather tokens.
|
|
34
|
-
for (let i = 0, length = value.length; i < length; i++) {
|
|
35
|
-
switch (value.charCodeAt(i)) {
|
|
36
|
-
case 0x20:
|
|
37
|
-
// ' '.
|
|
38
|
-
if (start === end) {
|
|
39
|
-
start = end = i + 1;
|
|
40
|
-
}
|
|
41
|
-
break;
|
|
42
|
-
case 0x2c:
|
|
43
|
-
// ','.
|
|
44
|
-
tokens.push(value.substring(start, end));
|
|
45
|
-
start = end = i + 1;
|
|
46
|
-
break;
|
|
47
|
-
default:
|
|
48
|
-
end = i + 1;
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Final token.
|
|
53
|
-
tokens.push(value.substring(start, end));
|
|
54
|
-
return tokens;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* @function isRangeFresh
|
|
58
|
-
* @description Check if request range fresh.
|
|
59
|
-
* @param context Koa context.
|
|
60
|
-
*/
|
|
61
|
-
function isRangeFresh(context) {
|
|
62
|
-
const { request, response } = context;
|
|
63
|
-
const ifRange = request.get('If-Range');
|
|
64
|
-
// No If-Range.
|
|
65
|
-
if (!ifRange) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
// If-Range as etag.
|
|
69
|
-
if (isETag(ifRange)) {
|
|
70
|
-
const etag = response.get('ETag');
|
|
71
|
-
return !!(etag && isETagFresh(ifRange, etag));
|
|
72
|
-
}
|
|
73
|
-
// If-Range as modified date.
|
|
74
|
-
const lastModified = response.get('Last-Modified');
|
|
75
|
-
return Date.parse(lastModified) <= Date.parse(ifRange);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* @function isETagFresh
|
|
79
|
-
* @description Check if etag is fresh.
|
|
80
|
-
* @param match The match value.
|
|
81
|
-
* @param etag The etag value.
|
|
82
|
-
*/
|
|
83
|
-
function isETagFresh(match, etag) {
|
|
84
|
-
return parseTokens(match).some(match => {
|
|
85
|
-
return match === etag || match === 'W/' + etag || 'W/' + match === etag;
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* @function decodeURI
|
|
90
|
-
* @description Decode URI component.
|
|
91
|
-
* @param URI The URI to decode.
|
|
92
|
-
*/
|
|
93
|
-
function decodeURI(URI) {
|
|
94
|
-
try {
|
|
95
|
-
return decodeURIComponent(URI);
|
|
96
|
-
} catch (_a) {
|
|
97
|
-
return -1;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* @function isConditionalGET
|
|
102
|
-
* @description Check if request is conditional GET.
|
|
103
|
-
* @param context Koa context.
|
|
104
|
-
*/
|
|
105
|
-
function isConditionalGET(context) {
|
|
106
|
-
const { request } = context;
|
|
107
|
-
return !!(
|
|
108
|
-
request.get('If-Match') ||
|
|
109
|
-
request.get('If-None-Match') ||
|
|
110
|
-
request.get('If-Modified-Since') ||
|
|
111
|
-
request.get('If-Unmodified-Since')
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* @function isPreconditionFailure
|
|
116
|
-
* @description Check if request precondition failure.
|
|
117
|
-
* @param context Koa context.
|
|
118
|
-
*/
|
|
119
|
-
function isPreconditionFailure({ request, response }) {
|
|
120
|
-
// If-Match.
|
|
121
|
-
const match = request.get('If-Match');
|
|
122
|
-
// Check if request match.
|
|
123
|
-
if (match) {
|
|
124
|
-
// Etag.
|
|
125
|
-
const etag = response.get('ETag');
|
|
126
|
-
return !etag || match === '*' || !isETagFresh(match, etag);
|
|
127
|
-
}
|
|
128
|
-
// If-Unmodified-Since.
|
|
129
|
-
const unmodifiedSince = Date.parse(request.get('If-Unmodified-Since'));
|
|
130
|
-
// Check if request unmodified.
|
|
131
|
-
if (!Number.isNaN(unmodifiedSince)) {
|
|
132
|
-
// Last-Modified.
|
|
133
|
-
const lastModified = Date.parse(response.get('Last-Modified'));
|
|
134
|
-
return Number.isNaN(lastModified) || lastModified > unmodifiedSince;
|
|
135
|
-
}
|
|
136
|
-
// Check precondition passed.
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* @function parseRanges
|
|
141
|
-
* @description Parse ranges.
|
|
142
|
-
* @param context Koa context.
|
|
143
|
-
* @param stats File stats.
|
|
144
|
-
*/
|
|
145
|
-
function parseRanges(context, stats) {
|
|
146
|
-
const { size } = stats;
|
|
147
|
-
// Range support.
|
|
148
|
-
if (/^bytes$/i.test(context.response.get('Accept-Ranges'))) {
|
|
149
|
-
const range = context.request.get('Range');
|
|
150
|
-
// Range fresh.
|
|
151
|
-
if (range && isRangeFresh(context)) {
|
|
152
|
-
// Parse range -1 -2 or [].
|
|
153
|
-
const parsed = parseRange(size, range, { combine: true });
|
|
154
|
-
// -1 signals an unsatisfiable range.
|
|
155
|
-
// -2 signals a malformed header string.
|
|
156
|
-
if (parsed === -1 || parsed === -2) {
|
|
157
|
-
return parsed;
|
|
158
|
-
}
|
|
159
|
-
// Ranges ok, support multiple ranges.
|
|
160
|
-
if (parsed.type === 'bytes') {
|
|
161
|
-
// Set 206 status.
|
|
162
|
-
context.status = 206;
|
|
163
|
-
const { length } = parsed;
|
|
164
|
-
// Multiple ranges.
|
|
165
|
-
if (length > 1) {
|
|
166
|
-
// Content-Length.
|
|
167
|
-
let contentLength = 0;
|
|
168
|
-
// Ranges.
|
|
169
|
-
const ranges = [];
|
|
170
|
-
// Range boundary.
|
|
171
|
-
const boundary = `<${generate()}>`;
|
|
172
|
-
// Range suffix.
|
|
173
|
-
const suffix = `\r\n--${boundary}--\r\n`;
|
|
174
|
-
// Multipart Content-Type.
|
|
175
|
-
const contentType = `Content-Type: ${context.type}`;
|
|
176
|
-
// Override Content-Type.
|
|
177
|
-
context.type = `multipart/byteranges; boundary=${boundary}`;
|
|
178
|
-
// Map ranges.
|
|
179
|
-
for (let index = 0; index < length; index++) {
|
|
180
|
-
const { start, end } = parsed[index];
|
|
181
|
-
// The first prefix boundary no \r\n.
|
|
182
|
-
const head = index > 0 ? '\r\n' : '';
|
|
183
|
-
const contentRange = `Content-Range: bytes ${start}-${end}/${size}`;
|
|
184
|
-
const prefix = `${head}--${boundary}\r\n${contentType}\r\n${contentRange}\r\n\r\n`;
|
|
185
|
-
// Compute Content-Length
|
|
186
|
-
contentLength += end - start + 1 + Buffer.byteLength(prefix);
|
|
187
|
-
// Cache range.
|
|
188
|
-
ranges.push({ start, end, prefix });
|
|
189
|
-
}
|
|
190
|
-
// The last add suffix boundary.
|
|
191
|
-
ranges[length - 1].suffix = suffix;
|
|
192
|
-
// Compute Content-Length.
|
|
193
|
-
contentLength += Buffer.byteLength(suffix);
|
|
194
|
-
// Set Content-Length.
|
|
195
|
-
context.length = contentLength;
|
|
196
|
-
// Return ranges.
|
|
197
|
-
return ranges;
|
|
198
|
-
} else {
|
|
199
|
-
const [{ start, end }] = parsed;
|
|
200
|
-
// Set Content-Length.
|
|
201
|
-
context.length = end - start + 1;
|
|
202
|
-
// Set Content-Range.
|
|
203
|
-
context.set('Content-Range', `bytes ${start}-${end}/${size}`);
|
|
204
|
-
// Return ranges.
|
|
205
|
-
return parsed;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// Set Content-Length.
|
|
211
|
-
context.length = size;
|
|
212
|
-
// Return ranges.
|
|
213
|
-
return [{ start: 0, end: Math.max(size - 1) }];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export { decodeURI, isConditionalGET, isPreconditionFailure, parseRanges };
|