@parcel/utils 2.0.0-beta.1 → 2.0.0-nightly.1002

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 (93) hide show
  1. package/.eslintrc.js +6 -6
  2. package/lib/index.js +35850 -337
  3. package/lib/index.js.map +1 -0
  4. package/package.json +42 -20
  5. package/src/DefaultMap.js +1 -1
  6. package/src/PromiseQueue.js +16 -12
  7. package/src/alternatives.js +143 -0
  8. package/src/ansi-html.js +2 -2
  9. package/src/blob.js +2 -1
  10. package/src/bundle-url.js +1 -1
  11. package/src/collection.js +14 -14
  12. package/src/config.js +93 -53
  13. package/src/countLines.js +5 -2
  14. package/src/debounce.js +1 -1
  15. package/src/dependency-location.js +11 -6
  16. package/src/generateBuildMetrics.js +5 -5
  17. package/src/generateCertificate.js +1 -1
  18. package/src/getCertificate.js +1 -1
  19. package/src/getExisting.js +1 -4
  20. package/src/getRootDir.js +1 -2
  21. package/src/glob.js +36 -5
  22. package/src/hash.js +34 -0
  23. package/src/http-server.js +4 -11
  24. package/src/index.js +47 -20
  25. package/src/is-url.js +1 -1
  26. package/src/isDirectoryInside.js +4 -1
  27. package/src/openInBrowser.js +3 -1
  28. package/src/path.js +17 -2
  29. package/src/prettyDiagnostic.js +39 -27
  30. package/src/relativeBundlePath.js +5 -7
  31. package/src/replaceBundleReferences.js +50 -34
  32. package/src/schema.js +96 -42
  33. package/src/shared-buffer.js +24 -0
  34. package/src/sourcemap.js +84 -10
  35. package/src/urlJoin.js +3 -1
  36. package/test/DefaultMap.test.js +7 -4
  37. package/test/config.test.js +50 -0
  38. package/test/input/config/config.json +3 -0
  39. package/test/input/config/empty.json +0 -0
  40. package/test/input/config/empty.toml +0 -0
  41. package/test/input/sourcemap/referenced-min.js +1 -1
  42. package/test/replaceBundleReferences.test.js +268 -0
  43. package/test/sourcemap.test.js +5 -9
  44. package/test/throttle.test.js +1 -2
  45. package/test/urlJoin.test.js +37 -0
  46. package/lib/DefaultMap.js +0 -64
  47. package/lib/Deferred.js +0 -26
  48. package/lib/PromiseQueue.js +0 -133
  49. package/lib/TapStream.js +0 -38
  50. package/lib/ansi-html.js +0 -16
  51. package/lib/blob.js +0 -31
  52. package/lib/bundle-url.js +0 -43
  53. package/lib/collection.js +0 -62
  54. package/lib/config.js +0 -109
  55. package/lib/countLines.js +0 -18
  56. package/lib/debounce.js +0 -20
  57. package/lib/dependency-location.js +0 -21
  58. package/lib/escape-html.js +0 -24
  59. package/lib/escape-markdown.js +0 -15
  60. package/lib/generateBuildMetrics.js +0 -124
  61. package/lib/generateCertificate.js +0 -124
  62. package/lib/getCertificate.js +0 -19
  63. package/lib/getExisting.js +0 -23
  64. package/lib/getRootDir.js +0 -55
  65. package/lib/glob.js +0 -69
  66. package/lib/http-server.js +0 -81
  67. package/lib/is-url.js +0 -17
  68. package/lib/isDirectoryInside.js +0 -16
  69. package/lib/md5.js +0 -40
  70. package/lib/objectHash.js +0 -26
  71. package/lib/openInBrowser.js +0 -70
  72. package/lib/parseCSSImport.js +0 -16
  73. package/lib/path.js +0 -30
  74. package/lib/prettifyTime.js +0 -10
  75. package/lib/prettyDiagnostic.js +0 -75
  76. package/lib/promisify.js +0 -13
  77. package/lib/relativeBundlePath.js +0 -18
  78. package/lib/relativeUrl.js +0 -16
  79. package/lib/replaceBundleReferences.js +0 -166
  80. package/lib/resolve.js +0 -108
  81. package/lib/schema.js +0 -321
  82. package/lib/serializeObject.js +0 -28
  83. package/lib/sourcemap.js +0 -58
  84. package/lib/stream.js +0 -78
  85. package/lib/throttle.js +0 -16
  86. package/lib/urlJoin.js +0 -27
  87. package/src/.babelrc +0 -3
  88. package/src/escape-markdown.js +0 -10
  89. package/src/md5.js +0 -49
  90. package/src/promisify.js +0 -13
  91. package/src/resolve.js +0 -216
  92. package/src/serializeObject.js +0 -22
  93. package/test/escapeMarkdown.test.js +0 -29
@@ -8,7 +8,7 @@ export default async function generateCertificate(
8
8
  fs: FileSystem,
9
9
  cacheDir: string,
10
10
  host: ?string,
11
- ) {
11
+ ): Promise<{|cert: Buffer, key: Buffer|}> {
12
12
  let certDirectory = cacheDir;
13
13
 
14
14
  const privateKeyPath = path.join(certDirectory, 'private.pem');
@@ -5,7 +5,7 @@ import type {FileSystem} from '@parcel/fs';
5
5
  export default async function getCertificate(
6
6
  fs: FileSystem,
7
7
  options: HTTPSOptions,
8
- ) {
8
+ ): Promise<{|cert: Buffer, key: Buffer|}> {
9
9
  try {
10
10
  let cert = await fs.readFile(options.cert);
11
11
  let key = await fs.readFile(options.key);
@@ -14,10 +14,7 @@ export default function getExisting(
14
14
  return {
15
15
  source,
16
16
  minified: fs.existsSync(minifiedPath)
17
- ? fs
18
- .readFileSync(minifiedPath, 'utf8')
19
- .trim()
20
- .replace(/;$/, '')
17
+ ? fs.readFileSync(minifiedPath, 'utf8').trim().replace(/;$/, '')
21
18
  : source,
22
19
  };
23
20
  }
package/src/getRootDir.js CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  import type {FilePath} from '@parcel/types';
4
4
  import {isGlob} from './glob';
5
-
6
- const path = require('path');
5
+ import path from 'path';
7
6
 
8
7
  export default function getRootDir(files: Array<FilePath>): FilePath {
9
8
  let cur = null;
package/src/glob.js CHANGED
@@ -5,21 +5,52 @@ import type {FileSystem} from '@parcel/fs';
5
5
 
6
6
  import _isGlob from 'is-glob';
7
7
  import fastGlob, {type FastGlobOptions} from 'fast-glob';
8
- import {isMatch} from 'micromatch';
8
+ import {isMatch, makeRe, type Options} from 'micromatch';
9
9
  import {normalizeSeparators} from './path';
10
10
 
11
- export function isGlob(p: FilePath) {
11
+ export function isGlob(p: FilePath): any {
12
12
  return _isGlob(normalizeSeparators(p));
13
13
  }
14
14
 
15
- export function isGlobMatch(filePath: FilePath, glob: Glob) {
16
- return isMatch(filePath, normalizeSeparators(glob));
15
+ export function isGlobMatch(
16
+ filePath: FilePath,
17
+ glob: Glob | Array<Glob>,
18
+ opts?: Options,
19
+ ): any {
20
+ glob = Array.isArray(glob)
21
+ ? glob.map(normalizeSeparators)
22
+ : normalizeSeparators(glob);
23
+ return isMatch(filePath, glob, opts);
24
+ }
25
+
26
+ export function globToRegex(glob: Glob, opts?: Options): RegExp {
27
+ return makeRe(glob, opts);
17
28
  }
18
29
 
19
30
  export function globSync(
20
31
  p: FilePath,
21
- options: FastGlobOptions<FilePath>,
32
+ fs: FileSystem,
33
+ options?: FastGlobOptions<FilePath>,
22
34
  ): Array<FilePath> {
35
+ // $FlowFixMe
36
+ options = {
37
+ ...options,
38
+ fs: {
39
+ statSync: p => {
40
+ return fs.statSync(p);
41
+ },
42
+ lstatSync: p => {
43
+ // Our FileSystem interface doesn't have lstat support at the moment,
44
+ // but this is fine for our purposes since we follow symlinks by default.
45
+ return fs.statSync(p);
46
+ },
47
+ readdirSync: (p, opts) => {
48
+ return fs.readdirSync(p, opts);
49
+ },
50
+ },
51
+ };
52
+
53
+ // $FlowFixMe
23
54
  return fastGlob.sync(normalizeSeparators(p), options);
24
55
  }
25
56
 
package/src/hash.js ADDED
@@ -0,0 +1,34 @@
1
+ // @flow strict-local
2
+
3
+ import type {Readable} from 'stream';
4
+ import type {FileSystem} from '@parcel/fs';
5
+
6
+ import {objectSortedEntriesDeep} from './collection';
7
+ import {hashString, Hash} from '@parcel/hash';
8
+
9
+ export function hashStream(stream: Readable): Promise<string> {
10
+ let hash = new Hash();
11
+ return new Promise((resolve, reject) => {
12
+ stream.on('error', err => {
13
+ reject(err);
14
+ });
15
+ stream
16
+ .on('data', chunk => {
17
+ hash.writeBuffer(chunk);
18
+ })
19
+ .on('end', function () {
20
+ resolve(hash.finish());
21
+ })
22
+ .on('error', err => {
23
+ reject(err);
24
+ });
25
+ });
26
+ }
27
+
28
+ export function hashObject(obj: {+[string]: mixed, ...}): string {
29
+ return hashString(JSON.stringify(objectSortedEntriesDeep(obj)));
30
+ }
31
+
32
+ export function hashFile(fs: FileSystem, filePath: string): Promise<string> {
33
+ return hashStream(fs.createReadStream(filePath));
34
+ }
@@ -7,10 +7,9 @@ import type {FilePath, HTTPSOptions} from '@parcel/types';
7
7
  import type {FileSystem} from '@parcel/fs';
8
8
 
9
9
  import http from 'http';
10
- // $FlowFixMe Flow does not know of http2's existance
11
- import {createSecureServer} from 'http2';
10
+ import https from 'https';
12
11
  import nullthrows from 'nullthrows';
13
- import {getCertificate, generateCertificate} from '../';
12
+ import {getCertificate, generateCertificate} from './';
14
13
 
15
14
  type CreateHTTPServerOpts = {|
16
15
  https: ?(HTTPSOptions | boolean),
@@ -41,17 +40,11 @@ export async function createHTTPServer(
41
40
  options.host,
42
41
  );
43
42
 
44
- server = createSecureServer(
45
- {cert, key, allowHTTP1: true},
46
- options.listener,
47
- );
43
+ server = https.createServer({cert, key}, options.listener);
48
44
  } else {
49
45
  let {cert, key} = await getCertificate(options.inputFS, options.https);
50
46
 
51
- server = createSecureServer(
52
- {cert, key, allowHTTP1: true},
53
- options.listener,
54
- );
47
+ server = https.createServer({cert, key}, options.listener);
55
48
  }
56
49
 
57
50
  // HTTPServer#close only stops accepting new connections, and does not close existing ones.
package/src/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // @flow strict-local
2
2
  export type * from './config';
3
+ export type * from './Deferred';
3
4
  export type * from './generateBuildMetrics';
5
+ export type * from './http-server';
6
+ export type * from './path';
4
7
  export type * from './prettyDiagnostic';
8
+ export type * from './schema';
5
9
 
6
10
  export {default as countLines} from './countLines';
7
11
  export {default as generateBuildMetrics} from './generateBuildMetrics';
@@ -14,8 +18,6 @@ export {default as objectHash} from './objectHash';
14
18
  export {default as prettifyTime} from './prettifyTime';
15
19
  export {default as prettyDiagnostic} from './prettyDiagnostic';
16
20
  export {default as PromiseQueue} from './PromiseQueue';
17
- // flowlint-next-line untyped-import:off
18
- export {default as promisify} from './promisify';
19
21
  export {default as validateSchema} from './schema';
20
22
  export {default as TapStream} from './TapStream';
21
23
  export {default as urlJoin} from './urlJoin';
@@ -25,21 +27,46 @@ export {default as debounce} from './debounce';
25
27
  export {default as throttle} from './throttle';
26
28
  export {default as openInBrowser} from './openInBrowser';
27
29
 
28
- export * from './blob';
29
- export * from './collection';
30
- export * from './config';
31
- export * from './DefaultMap';
32
- export * from './Deferred';
33
- export * from './glob';
34
- export * from './md5';
35
- export * from './schema';
36
- export * from './http-server';
37
- export * from './path';
38
- export * from './replaceBundleReferences';
39
- export * from './stream';
40
- export * from './resolve';
41
- export * from './relativeBundlePath';
42
- export * from './ansi-html';
43
- export * from './escape-html';
44
- export * from './escape-markdown';
45
- export * from './sourcemap';
30
+ // Explicit re-exports instead of export * for lazy require performance
31
+ export {findAlternativeNodeModules, findAlternativeFiles} from './alternatives';
32
+ export {blobToBuffer, blobToString} from './blob';
33
+ export {
34
+ unique,
35
+ objectSortedEntries,
36
+ objectSortedEntriesDeep,
37
+ setDifference,
38
+ setIntersect,
39
+ setUnion,
40
+ } from './collection';
41
+ export {resolveConfig, resolveConfigSync, loadConfig} from './config';
42
+ export {DefaultMap, DefaultWeakMap} from './DefaultMap';
43
+ export {makeDeferredWithPromise} from './Deferred';
44
+ export {isGlob, isGlobMatch, globSync, glob, globToRegex} from './glob';
45
+ export {hashStream, hashObject, hashFile} from './hash';
46
+ export {SharedBuffer} from './shared-buffer';
47
+ export {fuzzySearch} from './schema';
48
+ export {createHTTPServer} from './http-server';
49
+ export {normalizePath, normalizeSeparators, relativePath} from './path';
50
+ export {
51
+ replaceURLReferences,
52
+ replaceInlineReferences,
53
+ } from './replaceBundleReferences';
54
+ export {
55
+ measureStreamLength,
56
+ readableFromStringOrBuffer,
57
+ bufferStream,
58
+ blobToStream,
59
+ streamFromPromise,
60
+ fallbackStream,
61
+ } from './stream';
62
+ export {relativeBundlePath} from './relativeBundlePath';
63
+ export {ansiHtml} from './ansi-html';
64
+ export {escapeHTML} from './escape-html';
65
+ export {
66
+ SOURCEMAP_RE,
67
+ SOURCEMAP_EXTENSIONS,
68
+ matchSourceMappingURL,
69
+ loadSourceMapUrl,
70
+ loadSourceMap,
71
+ remapSourceLocation,
72
+ } from './sourcemap';
package/src/is-url.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @flow
2
2
 
3
- const _isURL = require('is-url');
3
+ import _isURL from 'is-url';
4
4
 
5
5
  // Matches anchor (ie: #raptors)
6
6
  const ANCHOR_REGEXP = /^#/;
@@ -2,7 +2,10 @@
2
2
  import type {FilePath} from '@parcel/types';
3
3
  import path from 'path';
4
4
 
5
- export default function isDirectoryInside(child: FilePath, parent: FilePath) {
5
+ export default function isDirectoryInside(
6
+ child: FilePath,
7
+ parent: FilePath,
8
+ ): boolean {
6
9
  const relative = path.relative(parent, child);
7
10
  return !relative.startsWith('..') && !path.isAbsolute(relative);
8
11
  }
@@ -49,7 +49,9 @@ function getAppName(appName: string): string {
49
49
  export default async function openInBrowser(url: string, browser: string) {
50
50
  try {
51
51
  const options =
52
- typeof browser === 'string' ? {app: [getAppName(browser)]} : undefined;
52
+ typeof browser === 'string' && browser.length > 0
53
+ ? {app: [getAppName(browser)]}
54
+ : undefined;
53
55
 
54
56
  await open(url, options);
55
57
  } catch (err) {
package/src/path.js CHANGED
@@ -2,8 +2,13 @@
2
2
  import type {FilePath} from '@parcel/types';
3
3
  import path from 'path';
4
4
 
5
+ const ABSOLUTE_PATH_REGEX = /^([a-zA-Z]:){0,1}[\\/]+/;
5
6
  const SEPARATOR_REGEX = /[\\]+/g;
6
7
 
8
+ export function isAbsolute(filepath: string): boolean {
9
+ return ABSOLUTE_PATH_REGEX.test(filepath);
10
+ }
11
+
7
12
  export function normalizeSeparators(filePath: FilePath): FilePath {
8
13
  return filePath.replace(SEPARATOR_REGEX, '/');
9
14
  }
@@ -17,7 +22,12 @@ export function normalizePath(
17
22
  filePath: FilePath,
18
23
  leadingDotSlash: boolean = true,
19
24
  ): FilePath {
20
- if (leadingDotSlash && filePath[0] !== '.' && filePath[0] !== '/') {
25
+ if (
26
+ leadingDotSlash &&
27
+ (filePath[0] !== '.' ||
28
+ (filePath[1] !== '.' && filePath[1] !== '/' && filePath[1] !== '\\')) &&
29
+ !path.isAbsolute(filePath)
30
+ ) {
21
31
  return normalizeSeparators('./' + filePath);
22
32
  } else {
23
33
  return normalizeSeparators(filePath);
@@ -28,6 +38,11 @@ export function relativePath(
28
38
  from: string,
29
39
  to: string,
30
40
  leadingDotSlash: boolean = true,
31
- ) {
41
+ ): FilePath {
42
+ // Fast path
43
+ if (to.startsWith(from + '/')) {
44
+ return (leadingDotSlash ? './' : '') + to.slice(from.length + 1);
45
+ }
46
+
32
47
  return normalizePath(path.relative(from, to), leadingDotSlash);
33
48
  }
@@ -7,12 +7,15 @@ import mdAnsi from '@parcel/markdown-ansi';
7
7
  import chalk from 'chalk';
8
8
  import path from 'path';
9
9
  import nullthrows from 'nullthrows';
10
+ // $FlowFixMe
11
+ import terminalLink from 'terminal-link';
10
12
 
11
13
  export type AnsiDiagnosticResult = {|
12
14
  message: string,
13
15
  stack: string,
14
16
  codeframe: string,
15
17
  hints: Array<string>,
18
+ documentation: string,
16
19
  |};
17
20
 
18
21
  export default async function prettyDiagnostic(
@@ -24,17 +27,12 @@ export default async function prettyDiagnostic(
24
27
  origin,
25
28
  message,
26
29
  stack,
27
- codeFrame,
30
+ codeFrames,
28
31
  hints,
29
- filePath,
30
- language,
31
32
  skipFormatting,
33
+ documentationURL,
32
34
  } = diagnostic;
33
35
 
34
- if (filePath != null && options && !path.isAbsolute(filePath)) {
35
- filePath = path.join(options.projectRoot, filePath);
36
- }
37
-
38
36
  let result = {
39
37
  message:
40
38
  mdAnsi(`**${origin ?? 'unknown'}**: `) +
@@ -42,42 +40,50 @@ export default async function prettyDiagnostic(
42
40
  stack: '',
43
41
  codeframe: '',
44
42
  hints: [],
43
+ documentation: '',
45
44
  };
46
45
 
47
- if (codeFrame !== undefined) {
48
- let highlights = Array.isArray(codeFrame.codeHighlights)
49
- ? codeFrame.codeHighlights
50
- : [codeFrame.codeHighlights];
46
+ if (codeFrames != null) {
47
+ for (let codeFrame of codeFrames) {
48
+ let filePath = codeFrame.filePath;
49
+ if (filePath != null && options && !path.isAbsolute(filePath)) {
50
+ filePath = path.join(options.projectRoot, filePath);
51
+ }
51
52
 
52
- let code =
53
- codeFrame.code ??
54
- (options &&
55
- (await options.inputFS.readFile(nullthrows(filePath), 'utf8')));
53
+ let highlights = codeFrame.codeHighlights;
54
+ let code =
55
+ codeFrame.code ??
56
+ (options &&
57
+ (await options.inputFS.readFile(nullthrows(filePath), 'utf8')));
56
58
 
57
- if (code != null) {
58
- let formattedCodeFrame = formatCodeFrame(code, highlights, {
59
- useColor: true,
60
- syntaxHighlighting: true,
61
- language:
62
- // $FlowFixMe sketchy null checks do not matter here...
63
- language || (filePath ? path.extname(filePath).substr(1) : undefined),
64
- terminalWidth,
65
- });
59
+ let formattedCodeFrame = '';
60
+ if (code != null) {
61
+ formattedCodeFrame = formatCodeFrame(code, highlights, {
62
+ useColor: true,
63
+ syntaxHighlighting: true,
64
+ language:
65
+ // $FlowFixMe sketchy null checks do not matter here...
66
+ codeFrame.language ||
67
+ (filePath != null ? path.extname(filePath).substr(1) : undefined),
68
+ terminalWidth,
69
+ });
70
+ }
66
71
 
67
72
  result.codeframe +=
68
73
  typeof filePath !== 'string'
69
74
  ? ''
70
- : chalk.underline(
75
+ : chalk.gray.underline(
71
76
  `${filePath}:${highlights[0].start.line}:${highlights[0].start.column}\n`,
72
77
  );
73
78
  result.codeframe += formattedCodeFrame;
79
+ if (codeFrame !== codeFrames[codeFrames.length - 1]) {
80
+ result.codeframe += '\n\n';
81
+ }
74
82
  }
75
83
  }
76
84
 
77
85
  if (stack != null) {
78
86
  result.stack = stack;
79
- } else if (filePath != null && result.codeframe == null) {
80
- result.stack = filePath;
81
87
  }
82
88
 
83
89
  if (Array.isArray(hints) && hints.length) {
@@ -86,5 +92,11 @@ export default async function prettyDiagnostic(
86
92
  });
87
93
  }
88
94
 
95
+ if (documentationURL != null) {
96
+ result.documentation = terminalLink('Learn more', documentationURL, {
97
+ fallback: (text, url) => `${text}: ${url}`,
98
+ });
99
+ }
100
+
89
101
  return result;
90
102
  }
@@ -1,6 +1,6 @@
1
1
  // @flow strict-local
2
2
 
3
- import type {NamedBundle} from '@parcel/types';
3
+ import type {FilePath, NamedBundle} from '@parcel/types';
4
4
 
5
5
  import path from 'path';
6
6
  import {relativePath} from './path';
@@ -9,10 +9,8 @@ export function relativeBundlePath(
9
9
  from: NamedBundle,
10
10
  to: NamedBundle,
11
11
  opts: {|leadingDotSlash: boolean|} = {leadingDotSlash: true},
12
- ) {
13
- return relativePath(
14
- path.dirname(from.filePath),
15
- to.filePath,
16
- opts.leadingDotSlash,
17
- );
12
+ ): FilePath {
13
+ let fromPath = path.join(from.target.distDir, from.name);
14
+ let toPath = path.join(to.target.distDir, to.name);
15
+ return relativePath(path.dirname(fromPath), toPath, opts.leadingDotSlash);
18
16
  }
@@ -10,11 +10,11 @@ import type {
10
10
  NamedBundle,
11
11
  } from '@parcel/types';
12
12
 
13
- import invariant from 'assert';
14
13
  import {Readable} from 'stream';
15
14
  import nullthrows from 'nullthrows';
15
+ import invariant from 'assert';
16
16
  import URL from 'url';
17
- import {bufferStream, relativeBundlePath, urlJoin} from '../';
17
+ import {bufferStream, relativeBundlePath, urlJoin} from './';
18
18
 
19
19
  type ReplacementMap = Map<
20
20
  string /* dependency id */,
@@ -23,7 +23,7 @@ type ReplacementMap = Map<
23
23
 
24
24
  /*
25
25
  * Replaces references to dependency ids for URL dependencies with:
26
- * - in the case of an unresolvable url dependency, the original moduleSpecifier.
26
+ * - in the case of an unresolvable url dependency, the original specifier.
27
27
  * These are external requests that Parcel did not bundle.
28
28
  * - in the case of a reference to another bundle, the relative url to that
29
29
  * bundle from the current bundle.
@@ -44,39 +44,40 @@ export function replaceURLReferences({
44
44
  let replacements = new Map();
45
45
  let urlDependencies = [];
46
46
  bundle.traverse(node => {
47
- if (node.type === 'dependency' && node.value.isURL) {
47
+ if (node.type === 'dependency' && node.value.specifierType === 'url') {
48
48
  urlDependencies.push(node.value);
49
49
  }
50
50
  });
51
51
 
52
52
  for (let dependency of urlDependencies) {
53
- if (!dependency.isURL) {
53
+ if (dependency.specifierType !== 'url') {
54
54
  continue;
55
55
  }
56
56
 
57
- let resolved = bundleGraph.resolveExternalDependency(dependency, bundle);
57
+ let placeholder = dependency.meta?.placeholder ?? dependency.id;
58
+ invariant(typeof placeholder === 'string');
59
+
60
+ let resolved = bundleGraph.getReferencedBundle(dependency, bundle);
58
61
  if (resolved == null) {
59
- replacements.set(dependency.id, {
60
- from: dependency.id,
61
- to: dependency.moduleSpecifier,
62
+ replacements.set(placeholder, {
63
+ from: placeholder,
64
+ to: dependency.specifier,
62
65
  });
63
66
  continue;
64
67
  }
65
68
 
66
- invariant(resolved.type === 'bundle_group');
67
- let entryBundle = bundleGraph.getBundlesInBundleGroup(resolved.value).pop();
68
- if (entryBundle.isInline) {
69
+ if (resolved.bundleBehavior === 'inline') {
69
70
  // If a bundle is inline, it should be replaced with inline contents,
70
71
  // not a URL.
71
72
  continue;
72
73
  }
73
74
 
74
75
  replacements.set(
75
- dependency.id,
76
+ placeholder,
76
77
  getURLReplacement({
77
78
  dependency,
78
79
  fromBundle: bundle,
79
- toBundle: entryBundle,
80
+ toBundle: resolved,
80
81
  relative,
81
82
  }),
82
83
  );
@@ -121,13 +122,8 @@ export async function replaceInlineReferences({
121
122
  });
122
123
 
123
124
  for (let dependency of dependencies) {
124
- let resolved = bundleGraph.resolveExternalDependency(dependency, bundle);
125
- if (resolved == null || resolved.type === 'asset') {
126
- continue;
127
- }
128
-
129
- let [entryBundle] = bundleGraph.getBundlesInBundleGroup(resolved.value);
130
- if (!entryBundle.isInline) {
125
+ let entryBundle = bundleGraph.getReferencedBundle(dependency, bundle);
126
+ if (entryBundle?.bundleBehavior !== 'inline') {
131
127
  continue;
132
128
  }
133
129
 
@@ -135,15 +131,18 @@ export async function replaceInlineReferences({
135
131
  entryBundle,
136
132
  bundleGraph,
137
133
  );
138
- let packagedContents = (packagedBundle.contents instanceof Readable
139
- ? await bufferStream(packagedBundle.contents)
140
- : packagedBundle.contents
134
+ let packagedContents = (
135
+ packagedBundle.contents instanceof Readable
136
+ ? await bufferStream(packagedBundle.contents)
137
+ : packagedBundle.contents
141
138
  ).toString();
142
139
 
143
140
  let inlineType = nullthrows(entryBundle.getMainEntry()).meta.inlineType;
144
141
  if (inlineType == null || inlineType === 'string') {
142
+ let placeholder = dependency.meta?.placeholder ?? dependency.id;
143
+ invariant(typeof placeholder === 'string');
145
144
  replacements.set(
146
- dependency.id,
145
+ placeholder,
147
146
  getInlineReplacement(dependency, inlineType, packagedContents),
148
147
  );
149
148
  }
@@ -152,7 +151,7 @@ export async function replaceInlineReferences({
152
151
  return performReplacement(replacements, contents, map);
153
152
  }
154
153
 
155
- function getURLReplacement({
154
+ export function getURLReplacement({
156
155
  dependency,
157
156
  fromBundle,
158
157
  toBundle,
@@ -162,21 +161,38 @@ function getURLReplacement({
162
161
  fromBundle: NamedBundle,
163
162
  toBundle: NamedBundle,
164
163
  relative: boolean,
165
- |}) {
166
- let url = URL.parse(dependency.moduleSpecifier);
164
+ |}): {|from: string, to: string|} {
167
165
  let to;
166
+
167
+ let orig = URL.parse(dependency.specifier);
168
+
168
169
  if (relative) {
169
- url.pathname = relativeBundlePath(fromBundle, toBundle, {
170
- leadingDotSlash: false,
170
+ to = URL.format({
171
+ pathname: relativeBundlePath(fromBundle, toBundle, {
172
+ leadingDotSlash: false,
173
+ }),
174
+ hash: orig.hash,
171
175
  });
172
- to = URL.format(url);
176
+
177
+ // If the resulting path includes a colon character and doesn't start with a ./ or ../
178
+ // we need to add one so that the first part before the colon isn't parsed as a URL protocol.
179
+ if (to.includes(':') && !to.startsWith('./') && !to.startsWith('../')) {
180
+ to = './' + to;
181
+ }
173
182
  } else {
174
- url.pathname = nullthrows(toBundle.name);
175
- to = urlJoin(toBundle.target.publicUrl, URL.format(url));
183
+ to = urlJoin(
184
+ toBundle.target.publicUrl,
185
+ URL.format({
186
+ pathname: nullthrows(toBundle.name),
187
+ hash: orig.hash,
188
+ }),
189
+ );
176
190
  }
177
191
 
192
+ let placeholder = dependency.meta?.placeholder ?? dependency.id;
193
+ invariant(typeof placeholder === 'string');
178
194
  return {
179
- from: dependency.id,
195
+ from: placeholder,
180
196
  to,
181
197
  };
182
198
  }