fixes for Svelte compilation

- enable resolver patch on worker binary to ensure js imports work
on subsequent worker requests
- cache ts library content, and use unified interface for cache
- prepare for separate css outputs
This commit is contained in:
Damien Elmes 2021-03-21 14:12:00 +10:00
parent dbcfaadd79
commit 2658d9992e
2 changed files with 68 additions and 81 deletions

View file

@ -20,5 +20,7 @@ nodejs_binary(
name = "svelte_bin", name = "svelte_bin",
data = ["svelte_bin_ts"] + _deps, data = ["svelte_bin_ts"] + _deps,
entry_point = ":svelte.ts", entry_point = ":svelte.ts",
# should fix .js files being not found on subsequent worker requests
templated_args = ["--bazel_patch_module_resolver"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

View file

@ -16,38 +16,52 @@ let parsedCommandLine: ts.ParsedCommandLine = {
}, },
}; };
let tsText = ""; // We avoid hitting the filesystem for ts/d.ts files after initial startup - the
// .ts file we generate can be injected directly into our cache, and Bazel
// should restart us if the Svelte or TS typings change.
// largely taken from https://github.com/Asana/bazeltsc/blob/7dfa0ba2bd5eb9ee556e146df35cf793fad2d2c3/src/bazeltsc.ts (MIT) interface FileContent {
text: string;
version: number;
}
const fileContent: Map<string, FileContent> = new Map();
function getFileContent(path: string): FileContent {
let content = fileContent.get(path);
if (!content) {
content = {
text: ts.sys.readFile(path)!,
version: 0,
};
fileContent.set(path, content);
}
return content;
}
function updateFileContent(path: string, text: string): void {
let content = fileContent.get(path);
if (content) {
content.text = text;
content.version += 1;
} else {
content = {
text,
version: 0,
};
fileContent.set(path, content);
}
}
// based on https://github.com/Asana/bazeltsc/blob/7dfa0ba2bd5eb9ee556e146df35cf793fad2d2c3/src/bazeltsc.ts (MIT)
const languageServiceHost: ts.LanguageServiceHost = { const languageServiceHost: ts.LanguageServiceHost = {
getCompilationSettings: (): ts.CompilerOptions => parsedCommandLine.options, getCompilationSettings: (): ts.CompilerOptions => parsedCommandLine.options,
getNewLine: () => ts.sys.newLine,
getScriptFileNames: (): string[] => parsedCommandLine.fileNames, getScriptFileNames: (): string[] => parsedCommandLine.fileNames,
getScriptVersion: (fileName: string): string => { getScriptVersion: (path: string): string => {
if (fileName == parsedCommandLine.fileNames[0]) { return getFileContent(path).version.toString();
return require("crypto").createHash("md5").update(tsText).digest("hex");
} else {
// If the file's size or modified-timestamp changed, it's a different version.
return (
ts.sys.getFileSize!(fileName) +
":" +
ts.sys.getModifiedTime!(fileName)!.getTime()
);
}
}, },
getScriptSnapshot: (fileName: string): ts.IScriptSnapshot | undefined => { getScriptSnapshot: (path: string): ts.IScriptSnapshot | undefined => {
let text; // if (!ts.sys.fileExists(fileName)) {
if (fileName == parsedCommandLine.fileNames[0]) { const text = getFileContent(path).text;
// serve out generated ts file from memory, so we can avoid writing a temporary
// file that causes conflicts on Windows
text = tsText;
} else {
if (!ts.sys.fileExists(fileName)) {
return undefined;
} else {
text = ts.sys.readFile(fileName)!;
}
}
return { return {
getText: (start: number, end: number) => { getText: (start: number, end: number) => {
if (start === 0 && end === text.length) { if (start === 0 && end === text.length) {
@ -61,40 +75,7 @@ const languageServiceHost: ts.LanguageServiceHost = {
getChangeRange: ( getChangeRange: (
oldSnapshot: ts.IScriptSnapshot oldSnapshot: ts.IScriptSnapshot
): ts.TextChangeRange | undefined => { ): ts.TextChangeRange | undefined => {
const oldText = oldSnapshot.getText(0, oldSnapshot.getLength()); return undefined;
// Find the offset of the first char that differs between oldText and text
let firstDiff = 0;
while (
firstDiff < oldText.length &&
firstDiff < text.length &&
text[firstDiff] === oldText[firstDiff]
) {
firstDiff++;
}
// Find the offset of the last char that differs between oldText and text
let oldIndex = oldText.length;
let newIndex = text.length;
while (
oldIndex > firstDiff &&
newIndex > firstDiff &&
oldText[oldIndex - 1] === text[newIndex - 1]
) {
oldIndex--;
newIndex--;
}
return {
span: {
start: firstDiff,
length: oldIndex - firstDiff,
},
newLength: newIndex - firstDiff,
};
},
dispose: (): void => {
text = "";
}, },
}; };
}, },
@ -104,8 +85,8 @@ const languageServiceHost: ts.LanguageServiceHost = {
const languageService = ts.createLanguageService(languageServiceHost); const languageService = ts.createLanguageService(languageServiceHost);
function compile(tsPath, shims) { function compile(tsPath: string, tsLibs: string[]) {
parsedCommandLine.fileNames = [tsPath, ...shims]; parsedCommandLine.fileNames = [tsPath, ...tsLibs];
const program = languageService.getProgram()!; const program = languageService.getProgram()!;
const tsHost = ts.createCompilerHost(parsedCommandLine.options); const tsHost = ts.createCompilerHost(parsedCommandLine.options);
const createdFiles = {}; const createdFiles = {};
@ -138,12 +119,12 @@ function readFile(file) {
}); });
} }
async function writeDts(tsPath, dtsPath, shims) { async function writeDts(tsPath, dtsPath, tsLibs) {
const dtsSource = compile(tsPath, shims); const dtsSource = compile(tsPath, tsLibs);
await writeFile(dtsPath, dtsSource); await writeFile(dtsPath, dtsSource);
} }
function writeTs(svelteSource, sveltePath) { function writeTs(svelteSource, sveltePath, tsPath) {
let tsSource = svelte2tsx(svelteSource, { let tsSource = svelte2tsx(svelteSource, {
filename: sveltePath, filename: sveltePath,
strictMode: true, strictMode: true,
@ -153,11 +134,15 @@ function writeTs(svelteSource, sveltePath) {
// replace the "///<reference types="svelte" />" with a line // replace the "///<reference types="svelte" />" with a line
// turning off checking, as we'll use svelte-check for that // turning off checking, as we'll use svelte-check for that
codeLines[0] = "// @ts-nocheck"; codeLines[0] = "// @ts-nocheck";
// write to our global updateFileContent(tsPath, codeLines.join("\n"));
tsText = codeLines.join("\n");
} }
async function writeJs(source, inputFilename, outputPath) { async function writeJs(
source: string,
inputFilename: string,
outputJsPath: string,
outputCssPath: string
): Promise<void> {
const preprocessOptions = preprocess({}); const preprocessOptions = preprocess({});
preprocessOptions.filename = inputFilename; preprocessOptions.filename = inputFilename;
@ -166,7 +151,7 @@ async function writeJs(source, inputFilename, outputPath) {
const result = svelte.compile(processed.toString!(), { const result = svelte.compile(processed.toString!(), {
format: "esm", format: "esm",
generate: "dom", generate: "dom",
filename: outputPath, filename: outputJsPath,
}); });
// warnings are an error // warnings are an error
if (result.warnings.length > 0) { if (result.warnings.length > 0) {
@ -174,7 +159,7 @@ async function writeJs(source, inputFilename, outputPath) {
return; return;
} }
const outputSource = result.js.code; const outputSource = result.js.code;
await writeFile(outputPath, outputSource); await writeFile(outputJsPath, outputSource);
} catch (err) { } catch (err) {
console.log(`compile failed: ${err}`); console.log(`compile failed: ${err}`);
return; return;
@ -182,21 +167,21 @@ async function writeJs(source, inputFilename, outputPath) {
} }
async function compileSvelte(args) { async function compileSvelte(args) {
const [sveltePath, mjsPath, dtsPath, ...shims] = args; const [sveltePath, mjsPath, dtsPath, ...tsLibs] = args;
const svelteSource = await readFile(sveltePath); const cssPath = sveltePath + ".css";
const svelteSource = (await readFile(sveltePath)) as string;
// mock filename const mockTsPath = sveltePath + ".ts";
const tempTsPath = sveltePath + ".ts"; await writeTs(svelteSource, sveltePath, mockTsPath);
await writeDts(mockTsPath, dtsPath, tsLibs);
await writeTs(svelteSource, sveltePath); await writeJs(svelteSource, sveltePath, mjsPath, cssPath);
await writeDts(tempTsPath, dtsPath, shims);
await writeJs(svelteSource, sveltePath, mjsPath);
return true; return true;
} }
function main(args) { function main() {
if (worker.runAsWorker(process.argv)) { if (worker.runAsWorker(process.argv)) {
console.log = worker.log;
worker.log("Svelte running as a Bazel worker"); worker.log("Svelte running as a Bazel worker");
worker.runWorkerLoop(compileSvelte); worker.runWorkerLoop(compileSvelte);
} else { } else {
@ -208,6 +193,6 @@ function main(args) {
} }
if (require.main === module) { if (require.main === module) {
main(process.argv.slice(2)); main();
process.exitCode = 0; process.exitCode = 0;
} }