Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
mangs opened this issue May 22, 2024 · 1 comment · Fixed by #11288
Assignees
Labels
bug Something isn't working sourcemaps

Comments

@mangs
Copy link
Contributor

mangs commented May 22, 2024

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 mangs added the bug Something isn't working label May 22, 2024
@mangs mangs changed the title Sourcemap files sometimes mostly empty (67 bytes) when using Bun.build with sourcemap: 'external' Sourcemap files sometimes malformed, mostly empty (67 bytes) when using Bun.build with sourcemap: 'external' May 22, 2024
@paperdave paperdave self-assigned this May 23, 2024
@mangs
Copy link
Contributor Author

mangs commented May 24, 2024

@Jarred-Sumner @paperdave This is better but still isn't fixed. I no longer see a null byte in the output, but I now get the following error in the sourcemap visualizer when examining the app entrypoint and sourcemap:

The "mappings" field of the imported source map contains invalid data. Invalid VLQ data at index 6: Original name index 0 is invalid (there are 0 names).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working sourcemaps
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants