@atlaspack/reporter-dev-server 2.14.5-canary.49 → 2.14.5-canary.490

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +496 -0
  2. package/dist/HMRServer.js +220 -0
  3. package/dist/Server.js +408 -0
  4. package/dist/ServerDataProvider.js +2 -0
  5. package/dist/ServerReporter.js +135 -0
  6. package/dist/StaticServerDataProvider.js +51 -0
  7. package/dist/serverErrors.js +16 -0
  8. package/dist/types.js +2 -0
  9. package/lib/HMRServer.js +270 -0
  10. package/lib/Server.js +466 -0
  11. package/lib/ServerDataProvider.js +1 -0
  12. package/lib/ServerReporter.js +137 -17455
  13. package/lib/StaticServerDataProvider.js +58 -0
  14. package/lib/launch-editor.d.js +1 -0
  15. package/lib/serverErrors.js +20 -0
  16. package/lib/types/HMRServer.d.ts +54 -0
  17. package/lib/types/Server.d.ts +45 -0
  18. package/lib/types/ServerDataProvider.d.ts +33 -0
  19. package/lib/types/ServerReporter.d.ts +3 -0
  20. package/lib/types/StaticServerDataProvider.d.ts +23 -0
  21. package/lib/types/serverErrors.d.ts +4 -0
  22. package/lib/types/types.d.ts +34 -0
  23. package/lib/types.js +1 -0
  24. package/package.json +19 -11
  25. package/src/{HMRServer.js → HMRServer.ts} +61 -35
  26. package/src/{Server.js → Server.ts} +52 -38
  27. package/src/ServerDataProvider.ts +34 -0
  28. package/src/{ServerReporter.js → ServerReporter.ts} +3 -5
  29. package/src/StaticServerDataProvider.ts +72 -0
  30. package/src/launch-editor.d.ts +4 -0
  31. package/src/{serverErrors.js → serverErrors.ts} +6 -5
  32. package/src/types.ts +46 -0
  33. package/{src/templates → templates}/404.html +1 -1
  34. package/{src/templates → templates}/500.html +1 -1
  35. package/test/StaticServerDataProvider.test.ts +131 -0
  36. package/tsconfig.json +18 -0
  37. package/tsconfig.tsbuildinfo +1 -0
  38. package/lib/ServerReporter.js.map +0 -1
  39. package/src/types.js.flow +0 -56
@@ -1,5 +1,4 @@
1
- // @flow
2
-
1
+ // @ts-expect-error TS2307
3
2
  import type {DevServerOptions, Request, Response} from './types.js.flow';
4
3
  import type {
5
4
  BuildSuccessEvent,
@@ -51,15 +50,19 @@ const SLASH_REGEX = /\//g;
51
50
  export const SOURCES_ENDPOINT = '/__parcel_source_root';
52
51
  const EDITOR_ENDPOINT = '/__parcel_launch_editor';
53
52
  const TEMPLATE_404 = fs.readFileSync(
54
- path.join(__dirname, 'templates/404.html'),
53
+ path.join(__dirname, '..', 'templates/404.html'),
55
54
  'utf8',
56
55
  );
57
56
 
58
57
  const TEMPLATE_500 = fs.readFileSync(
59
- path.join(__dirname, 'templates/500.html'),
58
+ path.join(__dirname, '..', 'templates/500.html'),
60
59
  'utf8',
61
60
  );
62
- type NextFunction = (req: Request, res: Response, next?: (any) => any) => any;
61
+ type NextFunction = (
62
+ req: Request,
63
+ res: Response,
64
+ next?: (arg1?: any) => any,
65
+ ) => any;
63
66
 
64
67
  export default class Server {
65
68
  pending: boolean;
@@ -68,21 +71,24 @@ export default class Server {
68
71
  options: DevServerOptions;
69
72
  rootPath: string;
70
73
  bundleGraph: BundleGraph<PackagedBundle> | null;
71
- requestBundle: ?(bundle: PackagedBundle) => Promise<BuildSuccessEvent>;
72
- errors: Array<{|
73
- message: string,
74
- stack: ?string,
75
- frames: Array<FormattedCodeFrame>,
76
- hints: Array<string>,
77
- documentation: string,
78
- |}> | null;
79
- stopServer: ?() => Promise<void>;
74
+ requestBundle:
75
+ | ((bundle: PackagedBundle) => Promise<BuildSuccessEvent>)
76
+ | null
77
+ | undefined;
78
+ errors: Array<{
79
+ message: string;
80
+ stack: string | null | undefined;
81
+ frames: Array<FormattedCodeFrame>;
82
+ hints: Array<string>;
83
+ documentation: string;
84
+ }> | null;
85
+ stopServer: (() => Promise<void>) | null | undefined;
80
86
 
81
87
  constructor(options: DevServerOptions) {
82
88
  this.options = options;
83
89
  try {
84
90
  this.rootPath = new URL(options.publicUrl).pathname;
85
- } catch (e) {
91
+ } catch (e: any) {
86
92
  this.rootPath = options.publicUrl;
87
93
  }
88
94
  this.pending = true;
@@ -135,7 +141,7 @@ export default class Server {
135
141
  );
136
142
  }
137
143
 
138
- respond(req: Request, res: Response): mixed {
144
+ respond(req: Request, res: Response): unknown {
139
145
  if (this.middleware.some((handler) => handler(req, res))) return;
140
146
  let {pathname, search} = url.parse(req.originalUrl || req.url);
141
147
  if (pathname == null) {
@@ -281,7 +287,7 @@ export default class Server {
281
287
  invariant(this.requestBundle != null);
282
288
  try {
283
289
  await this.requestBundle(bundle);
284
- } catch (err) {
290
+ } catch (err: any) {
285
291
  this.send500(req, res);
286
292
  return;
287
293
  }
@@ -296,7 +302,7 @@ export default class Server {
296
302
  req: Request,
297
303
  res: Response,
298
304
  next: NextFunction,
299
- ): Promise<void> | Promise<mixed> {
305
+ ): Promise<undefined> | Promise<unknown> {
300
306
  return this.serve(
301
307
  this.options.outputFS,
302
308
  this.options.distDir,
@@ -312,7 +318,7 @@ export default class Server {
312
318
  req: Request,
313
319
  res: Response,
314
320
  next: NextFunction,
315
- ): Promise<mixed> {
321
+ ): Promise<unknown> {
316
322
  if (req.method !== 'GET' && req.method !== 'HEAD') {
317
323
  // method not allowed
318
324
  res.statusCode = 405;
@@ -325,7 +331,7 @@ export default class Server {
325
331
  try {
326
332
  var filePath = url.parse(req.url).pathname || '';
327
333
  filePath = decodeURIComponent(filePath);
328
- } catch (err) {
334
+ } catch (err: any) {
329
335
  return this.sendError(res, 400);
330
336
  }
331
337
 
@@ -343,7 +349,7 @@ export default class Server {
343
349
 
344
350
  try {
345
351
  var stat = await fs.stat(filePath);
346
- } catch (err) {
352
+ } catch (err: any) {
347
353
  if (err.code === 'ENOENT') {
348
354
  return next(req, res);
349
355
  }
@@ -370,9 +376,13 @@ export default class Server {
370
376
  cleanUrls: false,
371
377
  },
372
378
  {
379
+ // @ts-expect-error TS7006
373
380
  lstat: (path) => fs.stat(path),
381
+ // @ts-expect-error TS7006
374
382
  realpath: (path) => fs.realpath(path),
383
+ // @ts-expect-error TS7006
375
384
  createReadStream: (path, options) => fs.createReadStream(path, options),
385
+ // @ts-expect-error TS7006
376
386
  readdir: (path) => fs.readdir(path),
377
387
  },
378
388
  );
@@ -388,7 +398,7 @@ export default class Server {
388
398
  res.end(TEMPLATE_404);
389
399
  }
390
400
 
391
- send500(req: Request, res: Response): void | Response {
401
+ send500(req: Request, res: Response): undefined | Response {
392
402
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
393
403
  res.writeHead(500);
394
404
 
@@ -452,6 +462,7 @@ export default class Server {
452
462
  }
453
463
  for (const [context, options] of Object.entries(cfg)) {
454
464
  // each key is interpreted as context, and value as middleware options
465
+ // @ts-expect-error TS2345
455
466
  app.use(createProxyMiddleware(context, options));
456
467
  }
457
468
  } else {
@@ -459,10 +470,7 @@ export default class Server {
459
470
  configFilePath,
460
471
  fileInRoot,
461
472
  );
462
- if (
463
- // $FlowFixMe
464
- Object.prototype.toString.call(cfg) === '[object Module]'
465
- ) {
473
+ if (Object.prototype.toString.call(cfg) === '[object Module]') {
466
474
  cfg = cfg.default;
467
475
  }
468
476
 
@@ -520,20 +528,26 @@ export default class Server {
520
528
  this.stopServer = stop;
521
529
 
522
530
  server.listen(this.options.port, this.options.host);
523
- return new Promise((resolve, reject) => {
524
- server.once('error', (err) => {
525
- this.options.logger.error(
526
- ({
531
+ // @ts-expect-error TS2322
532
+ return new Promise(
533
+ (
534
+ resolve: (result: Promise<Server> | Server) => void,
535
+ reject: (error?: any) => void,
536
+ ) => {
537
+ server.once('error', (err) => {
538
+ this.options.logger.error({
539
+ // @ts-expect-error TS2345
527
540
  message: serverErrors(err, this.options.port),
528
- }: Diagnostic),
529
- );
530
- reject(err);
531
- });
541
+ } as Diagnostic);
542
+ reject(err);
543
+ });
532
544
 
533
- server.once('listening', () => {
534
- resolve(server);
535
- });
536
- });
545
+ server.once('listening', () => {
546
+ // @ts-expect-error TS2345
547
+ resolve(server);
548
+ });
549
+ },
550
+ );
537
551
  }
538
552
 
539
553
  async stop(): Promise<void> {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * API the dev-server requires from the main application.
3
+ *
4
+ * This interface is used to provide data to the dev-server from the main application.
5
+ * It is used to get the list of HTML bundles and to request a bundle to be rebuilt.
6
+ */
7
+ export interface ServerDataProvider {
8
+ /**
9
+ * Return relative file paths for every HTML bundle in the current build output.
10
+ *
11
+ * The returned paths are relative to the distribution directory on disk and
12
+ * can therefore be used as HTTP request targets by the dev-server.
13
+ *
14
+ * @returns List of `.html` bundle paths. If no bundle graph is available yet,
15
+ * an empty array should be returned.
16
+ */
17
+ getHTMLBundleFilePaths(): string[];
18
+
19
+ /**
20
+ * Attempt to (re)build the bundle that corresponds to the given request path.
21
+ *
22
+ * If a bundle matching `requestedPath` exists and a request handler has been
23
+ * provided, the implementation should trigger a rebuild and resolve the
24
+ * promise with "requested". Otherwise the promise must resolve with
25
+ * "not-found".
26
+ *
27
+ * @param requestedPath Path (relative to the distribution directory) that
28
+ * identifies the bundle file requested by the browser.
29
+ * @returns A promise that resolves to:
30
+ * - "requested" when the build has been successfully triggered, or
31
+ * - "not-found" when the bundle cannot be handled by the provider.
32
+ */
33
+ requestBundle(requestedPath: string): Promise<'requested' | 'not-found'>;
34
+ }
@@ -1,12 +1,10 @@
1
- // @flow
2
-
3
1
  import {Reporter} from '@atlaspack/plugin';
4
2
  import HMRServer from './HMRServer';
5
3
  import Server from './Server';
6
4
 
7
5
  let servers: Map<number, Server> = new Map();
8
6
  let hmrServers: Map<number, HMRServer> = new Map();
9
- export default (new Reporter({
7
+ export default new Reporter({
10
8
  async report({event, options, logger}) {
11
9
  let {serveOptions, hmrOptions} = options;
12
10
  let server = serveOptions ? servers.get(serveOptions.port) : undefined;
@@ -47,6 +45,7 @@ export default (new Reporter({
47
45
  port: serveOptions.port,
48
46
  host: hmrOptions.host,
49
47
  devServer,
48
+ // @ts-expect-error TS7006
50
49
  addMiddleware: (handler) => {
51
50
  server?.middleware.push(handler);
52
51
  },
@@ -93,7 +92,6 @@ export default (new Reporter({
93
92
  }
94
93
  if (hmrOptions && hmrServer) {
95
94
  await hmrServer.stop();
96
- // $FlowFixMe[prop-missing]
97
95
  hmrServers.delete(hmrServer.wss.options.port);
98
96
  }
99
97
  break;
@@ -140,4 +138,4 @@ export default (new Reporter({
140
138
  break;
141
139
  }
142
140
  },
143
- }): Reporter);
141
+ }) as Reporter;
@@ -0,0 +1,72 @@
1
+ import type {
2
+ BundleGraph,
3
+ PackagedBundle,
4
+ BuildSuccessEvent,
5
+ } from '@atlaspack/types';
6
+ import path from 'path';
7
+
8
+ import {ServerDataProvider} from './ServerDataProvider';
9
+
10
+ /**
11
+ * An implementation of ServerDataProvider provides data from a direct `bundleGraph`
12
+ * and `requestBundle` function.
13
+ */
14
+ export class StaticServerDataProvider implements ServerDataProvider {
15
+ private distDir: string;
16
+
17
+ private bundleGraph: BundleGraph<PackagedBundle> | null = null;
18
+
19
+ private requestBundleFn:
20
+ | ((bundle: PackagedBundle) => Promise<BuildSuccessEvent>)
21
+ | null = null;
22
+
23
+ constructor(distDir: string) {
24
+ this.distDir = distDir;
25
+ }
26
+
27
+ getHTMLBundleFilePaths(): string[] {
28
+ return (
29
+ this.bundleGraph
30
+ ?.getBundles()
31
+ .filter((b) => path.posix.extname(b.name) === '.html')
32
+ .map((b) => path.relative(this.distDir, b.filePath)) ?? []
33
+ );
34
+ }
35
+
36
+ async requestBundle(
37
+ requestedPath: string,
38
+ ): Promise<'requested' | 'not-found'> {
39
+ const bundle = this.bundleGraph?.getBundles().find((b) => {
40
+ const relativePath = path.relative(this.distDir, b.filePath);
41
+ return relativePath === requestedPath;
42
+ });
43
+
44
+ if (!bundle) {
45
+ return 'not-found';
46
+ }
47
+
48
+ if (!this.requestBundleFn) {
49
+ return 'not-found';
50
+ }
51
+
52
+ await this.requestBundleFn(bundle);
53
+
54
+ return 'requested';
55
+ }
56
+
57
+ /**
58
+ * Update the provider with the latest bundle graph and request function.
59
+ *
60
+ * This should be called after every successful build so that subsequent requests operate on fresh data.
61
+ *
62
+ * @param bundleGraph The most recent bundle graph representing the output of a build.
63
+ * @param requestBundleFn Function that will be called to (re)build a specific bundle on demand.
64
+ */
65
+ update(
66
+ bundleGraph: BundleGraph<PackagedBundle>,
67
+ requestBundleFn: (bundle: PackagedBundle) => Promise<BuildSuccessEvent>,
68
+ ) {
69
+ this.bundleGraph = bundleGraph;
70
+ this.requestBundleFn = requestBundleFn;
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ declare module 'launch-editor' {
2
+ function launchEditor(filePath: string): void;
3
+ export default launchEditor;
4
+ }
@@ -1,19 +1,20 @@
1
- // @flow
2
- export type ServerError = Error & {|
3
- code: string,
4
- |};
1
+ export type ServerError = Error & {
2
+ code: string;
3
+ };
5
4
 
6
5
  const serverErrorList = {
7
6
  EACCES: "You don't have access to bind the server to port {port}.",
8
7
  EADDRINUSE: 'There is already a process listening on port {port}.',
9
- };
8
+ } as const;
10
9
 
11
10
  export default function serverErrors(err: ServerError, port: number): string {
12
11
  let desc = `Error: ${
13
12
  err.code
14
13
  } occurred while setting up server on port ${port.toString()}.`;
15
14
 
15
+ // @ts-expect-error TS7053
16
16
  if (serverErrorList[err.code]) {
17
+ // @ts-expect-error TS7053
17
18
  desc = serverErrorList[err.code].replace(/{port}/g, port);
18
19
  }
19
20
 
package/src/types.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type {
2
+ ServerOptions,
3
+ PluginLogger,
4
+ HMROptions,
5
+ HTTPSOptions,
6
+ FilePath,
7
+ PackageManager,
8
+ } from '@atlaspack/types';
9
+ import type {FileSystem} from '@atlaspack/fs';
10
+ import type {HTTPServer} from '@atlaspack/utils';
11
+ import {IncomingMessage, ServerResponse} from 'http';
12
+
13
+ interface HTTPRequest extends IncomingMessage {
14
+ originalUrl?: string;
15
+ }
16
+
17
+ export type Request = HTTPRequest;
18
+ export type Response = ServerResponse;
19
+
20
+ export type DevServerOptions = ServerOptions & {
21
+ projectRoot: string;
22
+ publicUrl: string;
23
+ cacheDir: string;
24
+ inputFS: FileSystem;
25
+ outputFS: FileSystem;
26
+ logger: PluginLogger;
27
+ hmrOptions?: HMROptions | null | undefined;
28
+ packageManager: PackageManager;
29
+ };
30
+
31
+ // TODO: Figure out if there is a node.js type that could be imported with a complete ServerError
32
+ export type ServerError = Error & {
33
+ code: string;
34
+ };
35
+
36
+ export type HMRServerOptions = {
37
+ devServer?: HTTPServer;
38
+ addMiddleware?: (handler: (req: Request, res: Response) => boolean) => void;
39
+ port: number;
40
+ host?: string | null | undefined;
41
+ logger: PluginLogger;
42
+ https?: HTTPSOptions | boolean;
43
+ cacheDir: FilePath;
44
+ inputFS: FileSystem;
45
+ outputFS: FileSystem;
46
+ };
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -0,0 +1,131 @@
1
+ import assert from 'assert';
2
+ import sinon from 'sinon';
3
+ import path from 'path';
4
+
5
+ import {StaticServerDataProvider} from '../src/StaticServerDataProvider';
6
+
7
+ type MockBundle = {
8
+ name: string;
9
+ filePath: string;
10
+ };
11
+
12
+ describe('StaticServerDataProvider', () => {
13
+ describe('getHTMLBundleFilePaths', () => {
14
+ it('returns an empty array when no bundleGraph is set', () => {
15
+ const provider = new StaticServerDataProvider('/dist');
16
+ assert.deepStrictEqual(provider.getHTMLBundleFilePaths(), []);
17
+ });
18
+
19
+ it('filters only .html bundles and returns relative paths', () => {
20
+ const distDir = path.resolve('/project/dist');
21
+ const provider = new StaticServerDataProvider(distDir);
22
+
23
+ const bundles: MockBundle[] = [
24
+ {
25
+ name: 'index.html',
26
+ filePath: path.join(distDir, 'index.html'),
27
+ },
28
+ {
29
+ name: 'app.js',
30
+ filePath: path.join(distDir, 'app.js'),
31
+ },
32
+ {
33
+ name: 'about/index.html',
34
+ filePath: path.join(distDir, 'about', 'index.html'),
35
+ },
36
+ ];
37
+
38
+ const bundleGraphMock = {
39
+ getBundles: () => bundles,
40
+ } as any;
41
+
42
+ // We don't care about requestBundleFn for this test, provide a noop.
43
+ provider.update(bundleGraphMock, () => Promise.resolve({} as any));
44
+
45
+ const htmlPaths = provider.getHTMLBundleFilePaths().sort();
46
+ assert.deepStrictEqual(htmlPaths, ['about/index.html', 'index.html']);
47
+ });
48
+ });
49
+
50
+ describe('requestBundle', () => {
51
+ it('returns "not-found" when no bundleGraph is set', async () => {
52
+ const provider = new StaticServerDataProvider('/dist');
53
+ const result = await provider.requestBundle('index.html');
54
+ assert.strictEqual(result, 'not-found');
55
+ });
56
+
57
+ it('returns "not-found" when the bundle is not present in the graph', async () => {
58
+ const distDir = '/dist';
59
+ const bundles: MockBundle[] = [
60
+ {name: 'index.html', filePath: path.join(distDir, 'index.html')},
61
+ ];
62
+ const bundleGraphMock = {
63
+ getBundles: () => bundles,
64
+ } as any;
65
+
66
+ const provider = new StaticServerDataProvider(distDir);
67
+ provider.update(bundleGraphMock, () => Promise.resolve({} as any));
68
+
69
+ const result = await provider.requestBundle('missing.html');
70
+ assert.strictEqual(result, 'not-found');
71
+ });
72
+
73
+ it('returns "not-found" when requestBundleFn is not set', async () => {
74
+ const distDir = '/dist';
75
+ const bundles: MockBundle[] = [
76
+ {name: 'index.html', filePath: path.join(distDir, 'index.html')},
77
+ ];
78
+ const provider = new StaticServerDataProvider(distDir);
79
+ // Directly assign bundleGraph without a requestBundleFn
80
+ (provider as any).bundleGraph = {getBundles: () => bundles} as any;
81
+
82
+ const result = await provider.requestBundle('index.html');
83
+ assert.strictEqual(result, 'not-found');
84
+ });
85
+
86
+ it('calls requestBundleFn and returns "requested" for a matching bundle', async () => {
87
+ const distDir = '/dist';
88
+ const bundles: MockBundle[] = [
89
+ {name: 'index.html', filePath: path.join(distDir, 'index.html')},
90
+ ];
91
+ const bundleGraphMock = {
92
+ getBundles: () => bundles,
93
+ } as any;
94
+
95
+ const requestBundleFn = sinon.stub().resolves();
96
+
97
+ const provider = new StaticServerDataProvider(distDir);
98
+ provider.update(bundleGraphMock, requestBundleFn);
99
+
100
+ const result = await provider.requestBundle('index.html');
101
+
102
+ assert.strictEqual(result, 'requested');
103
+ sinon.assert.calledOnce(requestBundleFn);
104
+ sinon.assert.calledWithExactly(requestBundleFn, bundles[0]);
105
+ });
106
+
107
+ it('handles nested bundle paths correctly', async () => {
108
+ const distDir = '/dist';
109
+ const bundles: MockBundle[] = [
110
+ {
111
+ name: 'nested/index.html',
112
+ filePath: path.join(distDir, 'nested', 'index.html'),
113
+ },
114
+ ];
115
+ const bundleGraphMock = {
116
+ getBundles: () => bundles,
117
+ } as any;
118
+
119
+ const requestBundleFn = sinon.stub().resolves();
120
+
121
+ const provider = new StaticServerDataProvider(distDir);
122
+ provider.update(bundleGraphMock, requestBundleFn);
123
+
124
+ const result = await provider.requestBundle('nested/index.html');
125
+
126
+ assert.strictEqual(result, 'requested');
127
+ sinon.assert.calledOnce(requestBundleFn);
128
+ sinon.assert.calledWithExactly(requestBundleFn, bundles[0]);
129
+ });
130
+ });
131
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "include": ["src"],
4
+ "compilerOptions": {
5
+ "composite": true
6
+ },
7
+ "references": [
8
+ {
9
+ "path": "../../core/plugin/tsconfig.json"
10
+ },
11
+ {
12
+ "path": "../../core/types/tsconfig.json"
13
+ },
14
+ {
15
+ "path": "../../core/utils/tsconfig.json"
16
+ }
17
+ ]
18
+ }