bun icon indicating copy to clipboard operation
bun copied to clipboard

Sourcemap files sometimes malformed, mostly empty (67 bytes) when using `Bun.build` with `sourcemap: 'external'`

Open mangs opened this issue 9 months ago • 0 comments

What version of Bun is running?

1.1.9+bb13798d9

What platform is your computer?

Darwin 23.4.0 arm64 arm

What steps can reproduce the bug?

  1. Bundle app with Bun.build and the following configuration object:
{
  define: {},
  entrypoints: ['./src/index.mts'],
  external: [],
  format: 'esm',
  loader: {},
  minify: true,
  naming: {
    chunk: '[name].[hash].mjs',
    entry: '[name].mjs',
  },
  outdir: './dist',
  plugins: [],
  publicPath: '',
  root: '.',
  sourcemap: 'external',
  splitting: true,
  target: 'bun',
}
  1. Examine contents of outdir (dist/ in this case). Notice how some .map files are malformed and nearly empty. I'm seeing those files with filesize of 67 bytes.

What is the expected behavior?

Sourcemap files should contain source code

What do you see instead?

Malformed JSON at the start of the file. For example, here are the contents from a sourcemap from a route handler (67 bytes):

",
  "debugId": "E5F236DC4A50165064756e2164756e21",
  "names": []
}

[!NOTE] All the 67 byte files are malformed the same way and have these keys and shape. They look identical except for the debugId value.

Additional information

Here is the full output from a build run so you can see all the different files, types, and sizes (notice multiple 67 byte sourcemap files):

egoldstein@MacCWQXVG6G0V get.apigateway.lambda $ bun run build
$ bun run --silent delete:build-artifacts && bun run --silent build:bundle
Building application artifacts for production...

 1)  aws4fetch.esm.aac95038a0cfb70e.mjs      7.19 KiB    [C]
 2)  aws4fetch.esm.aac95038a0cfb70e.mjs.map  11.05 KiB   [S]
 3)  error.826f137d89aa8666.mjs              1.95 KiB    [C]
 4)  error.826f137d89aa8666.mjs.map          872 B       [S]
 5)  index.04c119a3beb16a35.mjs              445 B       [C]
 6)  index.04c119a3beb16a35.mjs.map          144 B       [S]
 7)  index.47ac3ed1118934d2.mjs              1.41 KiB    [C]
 8)  index.47ac3ed1118934d2.mjs.map          3.78 KiB    [S]
 9)  index.8b306d478767a477.mjs              331 B       [C]
10)  index.8b306d478767a477.mjs.map          11.13 KiB   [S]
11)  index.cc647d1f6d9b8e22.mjs              120.33 KiB  [C]
12)  index.cc647d1f6d9b8e22.mjs.map          67 B        [S]
13)  index.mjs                               4.19 KiB    [E]
14)  index.mjs.map                           37.18 KiB   [S]
15)  pageRoute.fbb963aa51542254.mjs          1.2 KiB     [C]
16)  pageRoute.fbb963aa51542254.mjs.map      67 B        [S]
17)  precacheRoute.e5f236dc4a501650.mjs      3.03 KiB    [C]
18)  precacheRoute.e5f236dc4a501650.mjs.map  67 B        [S]

     8 Chunks                                135.87 KiB
     1 Entry point                           4.19 KiB
     9 Sourcemaps                            64.32 KiB
     TOTAL SIZE                              204.38 KiB

Build success [8.62ms]

Rightmost column legend: [C] for chunk [E] for entry point [S] for sourcemap


FYI the 144 byte sourcemap isn't malformed but contains no useful sourcemap info:

{
  "version": 3,
  "sources": [],
  "sourcesContent": [
  ],
  "mappings": "",
  "debugId": "04C119A3BEB16A3564756e2164756e21",
  "names": []
}

The 872 byte sourcemap contains malformed text:

UAAa,EACpB,wBACA,eACA,cAAc,GACd,eAAe,QACf,mBAAmB,UACnB,OACA,SACA,SACqB,CACrB,MAAM,EAAgC,OAAO,QAAQ,GAAyB,CAAC,CAAC,EAC7E,IAAI,EAAE,EAAU,KAAS,mCAAmC,YAAmB,OAAS,EACxF,KAAK,IAAI,EACN,EAAuB,EAAe,+BAA+B,MAAmB,GACxF,EAAoB,EAAS,gCAAgC,QAAe,GAElF,MAAO;AAAA,gBACO;AAAA;AAAA,eAED;AAAA,0CAC2B;AAAA,iDACO;AAAA,QACzC;AAAA,QACA;AAAA,QACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cASM,GAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACvCtB,IAAS,UAAgB,CACvB,EACA,EACA,EAAkB,QAClB,EAAa,IACb,CACA,GAAI,EACF,EAAO,GAAU,EAAc,CAAO,MAEtC,GAAO,GAAU,CAAY,EAE/B,OAAO,EAAsB,EAAc,CAAU,GAG9C,UAAoB,CAC3B,EACA,EACA,EAAkB,QAClB,EAAa,IACb,CACA,GAAI,EACF,EAAO,GAAU,EAAc,CAAO,MAEtC,GAAO,GAAU,CAAY,EAE/B,OAAO,EAAsB,EAAc,CAAE,MAAO,CAAa,CAAC,EAAG,CAAU,GAGxE,UAAkB,CACzB,EACA,EACA,EAAkB,QAClB,CACA,OAAO,EAAiB,EAAc,EAAS,EAAU,GAAG",
  "debugId": "826F137D89AA866664756e2164756e21",
  "names": []
}

The 11.13 KiB sourcemap contains the same source code twice:

{
  "version": 3,
  "sources": ["../node_modules/@mangs/bun-utils/src/timeUtils.mts", "../node_modules/@mangs/bun-utils/src/timeUtils.mts"],
  "sourcesContent": [
    "/**\n * @file Time-related utilities.\n */\n\n// External Imports\nimport { nanoseconds } from 'bun';\n\n// Local Variables\nconst timeUnits = ['ns', 'μs', 'ms', 's'] as const;\n\n// Local Types\ntype TimeUnits = (typeof timeUnits)[number];\ninterface FormatOptions {\n  /**\n   * Override of the locale used to format and localize the time value.\n   */\n  localeOverride?: string;\n  /**\n   * Smallest time unit that can be displayed.\n   */\n  unitsMinimum?: TimeUnits;\n  /**\n   * Override of time units to display; supersedes `unitsMinimum`.\n   */\n  unitsOverride?: TimeUnits;\n}\n\n// Local Functions\n/**\n * Build a `Server-Timing` header to measure a performance metric using the provided values.\n * @param name        The name of the performance metric.\n * @param startTime   The recorded start time used to compute the metric duration; computed by subtracting the time at which this function is called by the start time. [Milliseconds is the unit recommended by the W3C](https://w3c.github.io/server-timing/#duration-attribute).\n * @param description A description of the metric.\n * @returns           A `Server-Timing` header tuple: [`'Server-Timing'`, `string`].\n * @example\n * ```ts\n * import { buildServerTimingHeader } from '@mangs/bun-utils/time';\n *\n * const startTime = performance.now();\n * // sometime later...\n * request.headers.append(...buildServerTimingHeader('metric', startTime, 'It measures everything'));\n * ```\n */\nfunction buildServerTimingHeader(name: string, startTime?: number, description?: string) {\n  const durationFormatted =\n    typeof startTime === 'number' ? `;dur=${(performance.now() - startTime).toFixed(2)}` : '';\n  const descriptionFormatted = description ? `;desc=\"${description}\"` : '';\n  return [`Server-Timing`, `${name}${durationFormatted}${descriptionFormatted}`] as const;\n}\n\n/**\n * Get a formatted string representing the time between the provided start time parameter and the\n * time the function is called. An optional options object can be provided to customize formatting.\n * @param startTime     Start time calculated by `Bun.nanoseconds()`.\n * @param formatOptions Options object for formatting customization.\n * @returns             Localized string showing elapsed time with units.\n */\nfunction getElapsedTimeFormatted(startTime: number, formatOptions?: FormatOptions) {\n  const { localeOverride, unitsMinimum = 'ms', unitsOverride } = formatOptions ?? {};\n\n  const endTime = nanoseconds();\n  let elapsedTime = endTime - startTime;\n  let timeIndex = 0;\n\n  if (unitsOverride) {\n    const exactIndex = timeUnits.indexOf(unitsOverride);\n    while (timeIndex < exactIndex) {\n      elapsedTime /= 1_000;\n      timeIndex += 1;\n    }\n  } else {\n    let isMinimumUnit = false;\n    while (elapsedTime > 1) {\n      if (timeUnits[timeIndex] === unitsMinimum) {\n        isMinimumUnit = true;\n      }\n      if (isMinimumUnit && elapsedTime <= 1_000) {\n        break;\n      }\n      elapsedTime /= 1_000;\n      timeIndex += 1;\n    }\n  }\n\n  const elapsedTimeLocalized = elapsedTime.toLocaleString(localeOverride, {\n    maximumFractionDigits: 2,\n    minimumFractionDigits: 2,\n  });\n  const units = timeUnits[timeIndex];\n  return `${elapsedTimeLocalized}${units}`;\n}\n\n/**\n * Measure the execution time of the passed-in function.\n * @param runner Function whose execution duration will be measured.\n * @returns      Object containing the elapsed time and the return value of the passed-in function.\n */\nasync function measureElapsedTime<T>(runner: () => T | Promise<T>) {\n  const startTime = nanoseconds();\n  const returnValue = await runner();\n  const elapsedTime = getElapsedTimeFormatted(startTime);\n  return { elapsedTime, returnValue };\n}\n\n/**\n * Measure the execution time of the passed-in function, then append to the request object a\n * `Server-Timing` header containing the specified metric name, the measured duration, and\n * optionally the metric description.\n * @param metricName        Name of the `Server-Timing` metric being measured.\n * @param request           `Request` object to which the `Server-Timing` header will be appended.\n * @param runner            Function whose execution duration will be measured.\n * @param metricDescription Optional description of the `Server-Timing` metric being measured.\n * @returns                 The return value of the passed-in function.\n * @example\n * ```ts\n * import { measureServerTiming } from '@mangs/bun-utils/time';\n *\n * const cmsContent = await measureServerTiming('cmsLoad', request, () =>\n *   getCmsContent('article1'),\n * );\n * ```\n */\nasync function measureServerTiming<T>(\n  metricName: string,\n  request: Request,\n  runner: () => T | Promise<T>,\n  metricDescription?: string,\n) {\n  const startTime = performance.now();\n  const value = await runner();\n  request.headers.append(...buildServerTimingHeader(metricName, startTime, metricDescription));\n  return value;\n}\n\n/**\n * Asynchronous sleep function using promises.\n * @param duration Length of time to sleep.\n * @returns        `Promise` that resolves when the specified duration expires.\n */\nfunction sleep(duration: number) {\n  return new Promise((resolve) => {\n    setTimeout(resolve, duration);\n  });\n}\n\n// Module Exports\nexport {\n  buildServerTimingHeader,\n  getElapsedTimeFormatted,\n  measureElapsedTime,\n  measureServerTiming,\n  sleep,\n};\nexport type { FormatOptions };\n",
  "/**\n * @file Time-related utilities.\n */\n\n// External Imports\nimport { nanoseconds } from 'bun';\n\n// Local Variables\nconst timeUnits = ['ns', 'μs', 'ms', 's'] as const;\n\n// Local Types\ntype TimeUnits = (typeof timeUnits)[number];\ninterface FormatOptions {\n  /**\n   * Override of the locale used to format and localize the time value.\n   */\n  localeOverride?: string;\n  /**\n   * Smallest time unit that can be displayed.\n   */\n  unitsMinimum?: TimeUnits;\n  /**\n   * Override of time units to display; supersedes `unitsMinimum`.\n   */\n  unitsOverride?: TimeUnits;\n}\n\n// Local Functions\n/**\n * Build a `Server-Timing` header to measure a performance metric using the provided values.\n * @param name        The name of the performance metric.\n * @param startTime   The recorded start time used to compute the metric duration; computed by subtracting the time at which this function is called by the start time. [Milliseconds is the unit recommended by the W3C](https://w3c.github.io/server-timing/#duration-attribute).\n * @param description A description of the metric.\n * @returns           A `Server-Timing` header tuple: [`'Server-Timing'`, `string`].\n * @example\n * ```ts\n * import { buildServerTimingHeader } from '@mangs/bun-utils/time';\n *\n * const startTime = performance.now();\n * // sometime later...\n * request.headers.append(...buildServerTimingHeader('metric', startTime, 'It measures everything'));\n * ```\n */\nfunction buildServerTimingHeader(name: string, startTime?: number, description?: string) {\n  const durationFormatted =\n    typeof startTime === 'number' ? `;dur=${(performance.now() - startTime).toFixed(2)}` : '';\n  const descriptionFormatted = description ? `;desc=\"${description}\"` : '';\n  return [`Server-Timing`, `${name}${durationFormatted}${descriptionFormatted}`] as const;\n}\n\n/**\n * Get a formatted string representing the time between the provided start time parameter and the\n * time the function is called. An optional options object can be provided to customize formatting.\n * @param startTime     Start time calculated by `Bun.nanoseconds()`.\n * @param formatOptions Options object for formatting customization.\n * @returns             Localized string showing elapsed time with units.\n */\nfunction getElapsedTimeFormatted(startTime: number, formatOptions?: FormatOptions) {\n  const { localeOverride, unitsMinimum = 'ms', unitsOverride } = formatOptions ?? {};\n\n  const endTime = nanoseconds();\n  let elapsedTime = endTime - startTime;\n  let timeIndex = 0;\n\n  if (unitsOverride) {\n    const exactIndex = timeUnits.indexOf(unitsOverride);\n    while (timeIndex < exactIndex) {\n      elapsedTime /= 1_000;\n      timeIndex += 1;\n    }\n  } else {\n    let isMinimumUnit = false;\n    while (elapsedTime > 1) {\n      if (timeUnits[timeIndex] === unitsMinimum) {\n        isMinimumUnit = true;\n      }\n      if (isMinimumUnit && elapsedTime <= 1_000) {\n        break;\n      }\n      elapsedTime /= 1_000;\n      timeIndex += 1;\n    }\n  }\n\n  const elapsedTimeLocalized = elapsedTime.toLocaleString(localeOverride, {\n    maximumFractionDigits: 2,\n    minimumFractionDigits: 2,\n  });\n  const units = timeUnits[timeIndex];\n  return `${elapsedTimeLocalized}${units}`;\n}\n\n/**\n * Measure the execution time of the passed-in function.\n * @param runner Function whose execution duration will be measured.\n * @returns      Object containing the elapsed time and the return value of the passed-in function.\n */\nasync function measureElapsedTime<T>(runner: () => T | Promise<T>) {\n  const startTime = nanoseconds();\n  const returnValue = await runner();\n  const elapsedTime = getElapsedTimeFormatted(startTime);\n  return { elapsedTime, returnValue };\n}\n\n/**\n * Measure the execution time of the passed-in function, then append to the request object a\n * `Server-Timing` header containing the specified metric name, the measured duration, and\n * optionally the metric description.\n * @param metricName        Name of the `Server-Timing` metric being measured.\n * @param request           `Request` object to which the `Server-Timing` header will be appended.\n * @param runner            Function whose execution duration will be measured.\n * @param metricDescription Optional description of the `Server-Timing` metric being measured.\n * @returns                 The return value of the passed-in function.\n * @example\n * ```ts\n * import { measureServerTiming } from '@mangs/bun-utils/time';\n *\n * const cmsContent = await measureServerTiming('cmsLoad', request, () =>\n *   getCmsContent('article1'),\n * );\n * ```\n */\nasync function measureServerTiming<T>(\n  metricName: string,\n  request: Request,\n  runner: () => T | Promise<T>,\n  metricDescription?: string,\n) {\n  const startTime = performance.now();\n  const value = await runner();\n  request.headers.append(...buildServerTimingHeader(metricName, startTime, metricDescription));\n  return value;\n}\n\n/**\n * Asynchronous sleep function using promises.\n * @param duration Length of time to sleep.\n * @returns        `Promise` that resolves when the specified duration expires.\n */\nfunction sleep(duration: number) {\n  return new Promise((resolve) => {\n    setTimeout(resolve, duration);\n  });\n}\n\n// Module Exports\nexport {\n  buildServerTimingHeader,\n  getElapsedTimeFormatted,\n  measureElapsedTime,\n  measureServerTiming,\n  sleep,\n};\nexport type { FormatOptions };\n"
  ],
  "mappings": "AA2CA,IAAS,UAAuB,CAAC,EAAc,EAAoB,EAAsB,CACvF,MAAM,SACG,IAAc,SAAW,SAAS,YAAY,IAAI,EAAI,GAAW,QAAQ,CAAC,IAAM,GACnF,EAAuB,EAAc,UAAU,KAAiB,GACtE,MAAO,CAAC,gBAAiB,GAAG,IAAO,IAAoB,GAAsB",
  "debugId": "8B306D478767A47764756e2164756e21",
  "names": []
}

[!NOTE] The aws4fetch sourcemap appears to be the only one without any issues (1 out of 9).

mangs avatar May 22 '24 21:05 mangs