vue-simple-compiler
Version:
A lib to compile Vue Single-File Component into plain JavaScript & CSS.
253 lines (228 loc) • 6.9 kB
text/typescript
// The version of source-map is v0.6.x since:
// 1. source-map v0.7.x doesn't support sync APIs.
// 2. vue/compiler-sfc also uses source-map v0.6.x.
import type { RawSourceMap } from 'source-map';
import { SourceMapConsumer, SourceMapGenerator } from 'source-map';
export type { RawSourceMap };
export type FileInfo = {
code: string;
// The "version" inside RawSourceMap is string type rather than number type
// since it's based on source-map v0.6.x.
sourceMap?: RawSourceMap | undefined;
};
export const getConsumerSources = (consumer: SourceMapConsumer): string[] => {
// source-map's type definition is incomplete
const con = consumer as any;
return con.sources;
};
export const getGeneratorSources = (
generator: SourceMapGenerator
): string[] => {
// source-map's type definition is incomplete
const gen = generator as any;
return gen._sources.toArray();
};
export const hackGeneratorProps = (
generator: SourceMapGenerator,
props: Partial<{
file: string;
sourceRoot: string;
sources: string[];
}>
) => {
// source-map's type definition is incomplete
const gen = generator as any;
if (props.file) {
gen._file = props.file;
}
if (props.sourceRoot) {
gen._sourceRoot = props.sourceRoot;
}
if (props.sources) {
gen._sources = {
toArray() {
return props.sources!;
},
indexOf(source: string) {
return props.sources!.indexOf(source);
},
};
}
};
const genJSON = (generator: SourceMapGenerator): RawSourceMap => {
// source-map's type definition is incomplete
const gen = generator as any;
return gen.toJSON();
};
/**
* Chain source maps of two code blocks.
* credit: https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/compileTemplate.ts
*/
export const chainSourceMap = (
oldMap: RawSourceMap | undefined,
newMap: RawSourceMap | undefined
): RawSourceMap | undefined => {
if (!oldMap) return newMap;
if (!newMap) return oldMap;
const oldMapConsumer = new SourceMapConsumer(oldMap);
const newMapConsumer = new SourceMapConsumer(newMap);
const mergedMapGenerator = new SourceMapGenerator();
newMapConsumer.eachMapping((m) => {
if (m.originalLine == null) {
return;
}
const origPosInOldMap = oldMapConsumer.originalPositionFor({
line: m.originalLine,
column: m.originalColumn,
});
if (origPosInOldMap.source == null) {
return;
}
mergedMapGenerator.addMapping({
generated: {
line: m.generatedLine,
column: m.generatedColumn,
},
original: {
line: origPosInOldMap.line ?? 0,
column: m.originalColumn,
},
source: origPosInOldMap.source,
name: origPosInOldMap.name ?? '',
});
});
const sourceFileSet = new Set<string>(
getGeneratorSources(mergedMapGenerator)
);
getConsumerSources(oldMapConsumer).forEach((sourceFile) => {
sourceFileSet.add(sourceFile);
});
const sources = Array.from(sourceFileSet);
hackGeneratorProps(mergedMapGenerator, {
file: oldMap.file,
sourceRoot: oldMap.sourceRoot,
sources,
});
sources.forEach((sourceFile) => {
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile);
if (sourceContent) {
mergedMapGenerator.setSourceContent(sourceFile, sourceContent);
}
});
return genJSON(mergedMapGenerator);
};
/**
* Bundle source maps of multiple code blocks into one.
* - assume source roots of all the blocks are the same
* - pick the generated file name from the first block
*/
export const bundleSourceMap = (list: FileInfo[]): FileInfo => {
if (list.length === 1) {
return list[0];
}
let code = '';
let lineOffset = 0;
const generator = new SourceMapGenerator();
// for hack the source map
let firstSourceMap: RawSourceMap | undefined;
const sourceFileSet = new Set<string>();
const sourceFileMap = new Map<string, string>();
// const gen = generator as any
list.forEach((block) => {
code += `${block.code}\n`;
if (block.sourceMap) {
firstSourceMap = firstSourceMap || block.sourceMap;
block.sourceMap.sources.forEach((sourceFile, index) => {
sourceFileSet.add(sourceFile);
if (
block!.sourceMap!.sourcesContent![index] &&
!sourceFileMap.has(sourceFile)
) {
sourceFileMap.set(
sourceFile,
block!.sourceMap!.sourcesContent![index]
);
}
});
const consumer = new SourceMapConsumer(block.sourceMap);
consumer.eachMapping((m) => {
generator.addMapping({
generated: {
line: m.generatedLine + lineOffset,
column: m.generatedColumn,
},
original: {
line: m.originalLine,
column: m.originalColumn,
},
source: m.source,
name: m.name,
});
});
}
lineOffset += block.code.split(/\r?\n/).length;
});
if (firstSourceMap) {
const sources = Array.from(sourceFileSet);
hackGeneratorProps(generator, {
file: firstSourceMap.file,
sourceRoot: firstSourceMap.sourceRoot,
sources,
});
sources.forEach((sourceFile) => {
const sourceContent = sourceFileMap.get(sourceFile);
if (sourceContent) {
generator.setSourceContent(sourceFile, sourceContent);
}
});
}
return { code, sourceMap: genJSON(generator) };
};
/**
* Shift the source map of a single source file.
* It's used for adjusting the source map when you generate it from a block of the whole file, which causes line offset.
*/
export const shiftSourceMap = (
map: RawSourceMap,
offset: number,
newFileMap?: Record<string, { name: string; code: string }>
): RawSourceMap => {
const consumer = new SourceMapConsumer(map);
const generator = new SourceMapGenerator();
const sourceFileSet = new Set<string>();
const sourceFileMap = new Map<string, string>();
consumer.eachMapping((m) => {
const source = (newFileMap || {})[m.source] || {
name: m.source,
code: consumer.sourceContentFor(m.source) || '',
};
sourceFileSet.add(source.name);
if (source.code) {
sourceFileMap.set(source.name, source.code);
}
generator.addMapping({
generated: {
line: m.generatedLine,
column: m.generatedColumn,
},
original: {
line: m.originalLine + offset,
column: m.originalColumn,
},
source: source.name,
name: m.name,
});
});
hackGeneratorProps(generator, {
file: map.file,
sourceRoot: map.sourceRoot,
sources: Array.from(sourceFileSet),
});
Array.from(sourceFileSet).forEach((sourceFile) => {
const sourceContent = sourceFileMap.get(sourceFile);
if (sourceContent) {
generator.setSourceContent(sourceFile, sourceContent);
}
});
return genJSON(generator);
};