deno.land / x / lume@v2.1.4 / plugins / source_maps.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207import { isUrl, normalizePath } from "../core/utils/path.ts";import { merge } from "../core/utils/object.ts";import { log } from "../core/utils/log.ts";import { read } from "../core/utils/read.ts";import { concurrent } from "../core/utils/concurrent.ts";import { encodeBase64 } from "../deps/base64.ts";import { Page } from "../core/file.ts";import { basename, join, toFileUrl } from "../deps/path.ts";
import type Site from "../core/site.ts";
export interface Options { /** Set true to inline the source map in the output file */ inline?: boolean;
/** Set true to include the content of the source files */ sourceContent?: boolean;}
export const defaults: Options = { inline: false, sourceContent: false,};
/** Generate the source map files of assets */export default function (userOptions?: Options) { const options = merge(defaults, userOptions);
return (site: Site) => { site._data.enableSourceMap = true;
site.process( "*", (pages, allPages) => concurrent(pages, (page) => processSourceMap(page, allPages)), );
async function processSourceMap(file: Page, files: Page[]) { const sourceMap = file.data.sourceMap as SourceMap | undefined; file.data.sourceMap = undefined;
if (!sourceMap) { return; }
// Add the content of the source files try { if (options.sourceContent) { sourceMap.sourcesContent = await Promise.all( sourceMap.sources.map((url: string) => { const content = sourceMap[dynamicSourcesSymbol]?.[url];
return content ? content : read(url, false); }), ); } } catch (err) { log.error(`${err.message}\n${sourceMap.sources.join("\n")}`); }
// Relative paths (eg. "../bar") look better in the dev-tools. sourceMap.sourceRoot = toFileUrl(site.root()).href; sourceMap.sources = sourceMap.sources.map((url: string) => url.replace(sourceMap.sourceRoot!, "") );
// Inline the source map in the output file if (options.inline) { const url = `data:application/json;charset=utf-8;base64,${ encodeBase64(JSON.stringify(sourceMap)) }`; file.content += addSourceMap(file.outputPath, url); return; }
// Create a source map file const url = file.outputPath + ".map"; sourceMap.file = url; file.content += addSourceMap(file.outputPath, `./${basename(url)}`); files.push(Page.create({ url, content: JSON.stringify(sourceMap) })); } };}
/** Utilities to use by other plugins to manage source maps */export const dynamicSourcesSymbol = Symbol.for("dynamicSources");
/** SourceMap with a property to store dynamic sources */export interface SourceMap { version: number; file?: string; sources: readonly string[]; sourceRoot?: string; sourcesContent?: readonly (string | null)[]; names: readonly string[]; mappings: string; [dynamicSourcesSymbol]?: Record<string, string>;}
export interface PrepareResult { content: string; sourceMap?: SourceMap; filename: string; enableSourceMap: boolean;}
/** Return the required info to process a file */export function prepareAsset(site: Site, page: Page): PrepareResult { const enableSourceMap = !!site._data.enableSourceMap; const content = page.content as string; const sourceMap = enableSourceMap ? page.data.sourceMap as SourceMap | undefined : undefined; const filename = page.src.path ? site.src(page.src.path + page.src.ext) : site.src(page.outputPath); return { content, sourceMap, filename, enableSourceMap };}
/** Save the process result */export function saveAsset( site: Site, page: Page, content: string, sourceMap?: SourceMap | string,) { if (!site._data.enableSourceMap) { sourceMap = undefined; }
// There's no source map if (!sourceMap) { page.content = content; return; }
const root = site.root();
// Ensure the sourceMap is an object if (typeof sourceMap === "string") { sourceMap = JSON.parse(sourceMap) as SourceMap; }
// Normalize any source url function normalizeSource(source: string): string { if (source.startsWith("deno:")) { // esbuild source = source.substring(5); } if (isUrl(source)) { return source; }
source = normalizePath(source);
return source.startsWith(root) ? toFileUrl(decodeURIComponent(source)).href : toFileUrl(decodeURIComponent(join(root, source))).href; }
sourceMap.sources = sourceMap.sources .filter((source: string) => source !== "<no source>") // tailwindcss .map(normalizeSource);
// Inherit the dynamic sources from the previous source map const previousSourceMap = page.data.sourceMap as SourceMap | undefined; if (previousSourceMap) { sourceMap[dynamicSourcesSymbol] = previousSourceMap[dynamicSourcesSymbol]; }
// If it's a dynamic source (not from the file system), store it in the source map if (!page.src.path) { const sources = sourceMap[dynamicSourcesSymbol] || {}; const file = normalizeSource(site.src(page.outputPath)); sourceMap[dynamicSourcesSymbol] = sources;
if (!sources[file]) { sources[file] = page.content as string; } }
// Store the new content and source map page.data.sourceMap = sourceMap; page.content = content;}
function addSourceMap(url: string, sourceMap: string): string { if (url.endsWith(".js")) { return `\n//# sourceMappingURL=${sourceMap}`; }
// It's CSS return `\n/*# sourceMappingURL=${sourceMap} */`;}
/** Extends Data interface */declare global { namespace Lume { export interface Data { /** * The source map data (if it's an asset) * @see https://lume.land/plugins/source_maps/ */ sourceMap?: SourceMap; } }}
Version Info