diff --git a/deps.ts b/deps.ts index 0e49ac2..e9197ff 100644 --- a/deps.ts +++ b/deps.ts @@ -9,7 +9,11 @@ export { rollup, VERSION as rollupVersion } from 'npm:rollup@3.29.4'; export { loadSync as dotenvLoad } from 'jsr:@std/dotenv@0.218.2'; export { serve } from 'jsr:@std/http@0.224.0'; export { dirname, resolve } from 'jsr:@std/path@0.224.0'; -export { dirname as urlDirname, join as urlJoin } from 'jsr:@std/url@0.224.0'; +export { + basename as urlBasename, + dirname as urlDirname, + join as urlJoin, +} from 'jsr:@std/url@0.224.0'; export { default as request } from 'npm:request@2.88.2'; export { get as kvGet, set as kvSet } from 'jsr:@kitsonk/kv-toolbox@0.9.0/blob'; export { getBuildTargetFromUA } from 'npm:esm-compat@0.0.2'; diff --git a/src/create-request-handler.ts b/src/create-request-handler.ts index 3a88d22..9919a8c 100644 --- a/src/create-request-handler.ts +++ b/src/create-request-handler.ts @@ -11,7 +11,7 @@ import { nodeRequest, } from './utils.ts'; import { toSystemjs } from './to-systemjs.ts'; -import { getBuildTargetFromUA, opentelemetry } from '../deps.ts'; +import { getBuildTargetFromUA, opentelemetry, urlBasename } from '../deps.ts'; import type { Cache, Config, OpenTelemetry, ResponseProps } from './types.ts'; export function createRequestHandler( @@ -186,13 +186,23 @@ export function createRequestHandler( ) .toString(); const isRawRequest = selfUrl.searchParams.has('raw'); + const isMapRequest = publicSelfUrl.endsWith('.map'); rootSpan?.setAttributes({ 'esm.build.target': buildTarget, 'http.route': BASE_PATH, 'http.url': publicSelfUrl, }); + if (isMapRequest && !CACHE) { + // Sourcemaps are only enabled with CACHE + // otherwise they are served as inlined data uris + // thus it is not possible to receive a sourcemap request when !CACHE + return new Response(null, { status: 404 }); + } if (CACHE) { - const cacheKey = [publicSelfUrl, buildTarget]; + const cacheKey = [ + isMapRequest ? publicSelfUrl.slice(0, -4) : publicSelfUrl, + buildTarget, + ]; const cacheReadSpan = tracer.startSpan('cache-read', { attributes: { 'span.type': 'cache', @@ -202,6 +212,23 @@ export function createRequestHandler( const cachedValue = await cache?.get(cacheKey); cacheReadSpan.addEvent(cachedValue ? 'cache-hit' : 'cache-miss'); cacheReadSpan.end(); + if (isMapRequest) { + // A sourcemap request always should come after the original JS module + // thus, it MUST be already in the cache of the original JS module URL + // or we just return an 404 + if (cachedValue && cachedValue.map) { + return new Response(cachedValue.map, { + headers: { + 'access-control-allow-methods': '*', + 'access-control-allow-origin': '*', + 'cache-control': + 'public, max-age=31536000, immutable', + 'content-type': 'application/json; charset=utf-8', + }, + }); + } + return new Response(null, { status: 404 }); + } if (cachedValue) { const response = await createFinalResponse( { @@ -233,6 +260,7 @@ export function createRequestHandler( redirect: 'manual', }); let body = await upstreamResponse.text(); + let map: string | undefined = undefined; upstreamSpan.end(); if (!isRawRequest && isJsResponse(upstreamResponse)) { const sourcemapSpan = tracer.startSpan('sourcemap'); @@ -240,12 +268,25 @@ export function createRequestHandler( body, upstreamUrlString, ); + const canGenerateSourcemap = + !!(typeof sourceModule === 'object' && sourceModule.map); + const sourcemap = canGenerateSourcemap + ? (CACHE ? true : 'inline') + : false; + const sourcemapFileNames = sourcemap === true + ? `${urlBasename(publicSelfUrl)}.map` + : undefined; sourcemapSpan.end(); const buildSpan = tracer.startSpan('build'); const buildResult = await toSystemjs(sourceModule, { banner: OUTPUT_BANNER, + sourcemap, + sourcemapFileNames, }, config); body = replaceOrigin(buildResult.code); + if (sourcemap === true) { + map = buildResult.map; + } buildSpan.end(); } else { body = replaceOrigin(body); @@ -259,6 +300,7 @@ export function createRequestHandler( denyHeaders, replaceOriginHeaders, ), + map, status: upstreamResponse.status, statusText: upstreamResponse.statusText, }, diff --git a/src/to-systemjs.ts b/src/to-systemjs.ts index 380a9f9..1268a44 100644 --- a/src/to-systemjs.ts +++ b/src/to-systemjs.ts @@ -35,7 +35,6 @@ export const toSystemjsMain = async ( const outputOptions: OutputOptions = { dir: 'out', // not really used format: 'systemjs' as ModuleFormat, - sourcemap: mod.map ? 'inline' : false, ...rollupOutputOptions, footer: `/* rollup@${rollupVersion}${ rollupOutputOptions.footer ? ` - ${rollupOutputOptions.footer}` : '' diff --git a/src/types.ts b/src/types.ts index 29c89f4..57c6962 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,6 +37,7 @@ export interface HttpZResponseModel { export type ResponseProps = { url: string; body: string; + map?: string; headers: Headers; status: number; statusText: string;