sourceMapInitializer.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /**
  2. * Source Map Initializer
  3. *
  4. * This utility ensures source maps are properly loaded in production builds.
  5. * It attempts to preload source maps for all scripts on the page and
  6. * sets up global error handlers to enhance errors with source maps.
  7. *
  8. * This implementation is compatible with VSCode's Content Security Policy.
  9. */
  10. import { enhanceErrorWithSourceMaps } from "./sourceMapUtils"
  11. /**
  12. * Initialize source map support for production builds
  13. */
  14. export function initializeSourceMaps(): void {
  15. if (process.env.NODE_ENV !== "production") {
  16. // Only needed in production builds
  17. return
  18. }
  19. console.debug("Initializing CSP-compatible source map support for production build")
  20. // Set up global error handler
  21. window.addEventListener("error", async (event) => {
  22. if (event.error && event.error instanceof Error) {
  23. try {
  24. // Apply source maps to the error
  25. const enhancedError = await enhanceErrorWithSourceMaps(event.error)
  26. // Log the enhanced error
  27. console.error("Source mapped error:", enhancedError)
  28. // Don't prevent default handling - let the ErrorBoundary catch it
  29. } catch (e) {
  30. console.error("Error enhancing error with source maps:", e)
  31. }
  32. }
  33. })
  34. // Set up unhandled promise rejection handler
  35. window.addEventListener("unhandledrejection", async (event) => {
  36. if (event.reason && event.reason instanceof Error) {
  37. try {
  38. // Apply source maps to the error
  39. const enhancedError = await enhanceErrorWithSourceMaps(event.reason)
  40. // Log the enhanced error
  41. console.error("Source mapped rejection:", enhancedError)
  42. } catch (e) {
  43. console.error("Error enhancing rejection with source maps:", e)
  44. }
  45. }
  46. })
  47. // Preload source maps for all scripts
  48. try {
  49. const scripts = document.getElementsByTagName("script")
  50. for (let i = 0; i < scripts.length; i++) {
  51. const script = scripts[i]
  52. if (script.src) {
  53. // Try multiple source map locations
  54. const possibleMapUrls = [
  55. `${script.src}.map`,
  56. `${script.src}?source-map=true`,
  57. script.src.replace(/\.js$/, ".js.map"),
  58. script.src.replace(/\.js$/, ".map.json"),
  59. script.src.replace(/\.js$/, ".sourcemap"),
  60. ]
  61. // Preload all possible source map locations
  62. for (const mapUrl of possibleMapUrls) {
  63. const link = document.createElement("link")
  64. link.rel = "preload"
  65. link.as = "fetch"
  66. link.href = mapUrl
  67. link.crossOrigin = "anonymous"
  68. document.head.appendChild(link)
  69. }
  70. // Also check for inline sourceMappingURL comments
  71. fetch(script.src)
  72. .then((response) => response.text())
  73. .then((content) => {
  74. const sourceMappingURLMatch = content.match(/\/\/[#@]\s*sourceMappingURL=([^\s]+)/)
  75. if (sourceMappingURLMatch && sourceMappingURLMatch[1]) {
  76. const sourceMappingURL = sourceMappingURLMatch[1]
  77. // If it's not a data: URL, preload it
  78. if (!sourceMappingURL.startsWith("data:")) {
  79. const scriptUrlObj = new URL(script.src)
  80. const baseUrl = scriptUrlObj.href.substring(0, scriptUrlObj.href.lastIndexOf("/") + 1)
  81. const fullUrl = new URL(sourceMappingURL, baseUrl).href
  82. const link = document.createElement("link")
  83. link.rel = "preload"
  84. link.as = "fetch"
  85. link.href = fullUrl
  86. link.crossOrigin = "anonymous"
  87. document.head.appendChild(link)
  88. }
  89. }
  90. })
  91. .catch((e) => console.debug("Error checking for inline sourceMappingURL:", e))
  92. }
  93. }
  94. } catch (e) {
  95. console.error("Error preloading source maps:", e)
  96. }
  97. }
  98. /**
  99. * Expose source maps on the window object for debugging
  100. */
  101. export function exposeSourceMapsForDebugging(): void {
  102. if (process.env.NODE_ENV !== "production") {
  103. return
  104. }
  105. try {
  106. // Add a global function to manually apply source maps to an error
  107. ;(window as any).__applySourceMaps = async (error: Error) => {
  108. if (!(error instanceof Error)) {
  109. console.error("Not an Error object:", error)
  110. return error
  111. }
  112. return await enhanceErrorWithSourceMaps(error)
  113. }
  114. // Add a global function to test source map functionality
  115. ;(window as any).__testSourceMaps = () => {
  116. try {
  117. // Intentionally cause an error
  118. const obj: any = undefined
  119. obj.nonExistentMethod()
  120. } catch (e) {
  121. if (e instanceof Error) {
  122. console.log("Original error:", e)
  123. ;(window as any).__applySourceMaps(e).then((enhanced: Error) => {
  124. console.log("Enhanced error:", enhanced)
  125. // Log the source mapped stack if available
  126. if ("sourceMappedStack" in enhanced) {
  127. console.log("Source mapped stack:", enhanced.sourceMappedStack)
  128. }
  129. // Log the source mapped component stack if available
  130. if ("sourceMappedComponentStack" in enhanced) {
  131. console.log("Source mapped component stack:", enhanced.sourceMappedComponentStack)
  132. }
  133. })
  134. }
  135. }
  136. }
  137. // Add a global function to check if source maps are available for a script
  138. ;(window as any).__checkSourceMap = async (scriptUrl: string) => {
  139. try {
  140. const response = await fetch(`${scriptUrl}.map`)
  141. if (response.ok) {
  142. const sourceMap = await response.json()
  143. const originalFileName =
  144. sourceMap.sources && sourceMap.sources.length > 0 ? sourceMap.sources[0] : "unknown"
  145. console.log(`Source map found for ${scriptUrl}. Original file: ${originalFileName}`)
  146. return true
  147. } else {
  148. console.log(`No source map found for ${scriptUrl}`)
  149. return false
  150. }
  151. } catch (e) {
  152. console.error(`Error checking source map for ${scriptUrl}:`, e)
  153. return false
  154. }
  155. }
  156. console.debug("Source map debugging utilities exposed on window object")
  157. } catch (e) {
  158. console.error("Error exposing source maps for debugging:", e)
  159. }
  160. }