anki/ts/svelte/svelte.ts

222 lines
6.9 KiB
TypeScript
Raw Normal View History

2021-04-13 10:57:08 +02:00
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
// languageServerHost taken from MIT sources - see below.
import * as fs from "fs";
import * as worker from "@bazel/worker";
import { svelte2tsx } from "svelte2tsx";
import preprocess from "svelte-preprocess";
import { basename } from "path";
import * as ts from "typescript";
import * as svelte from "svelte/compiler.js";
let parsedCommandLine: ts.ParsedCommandLine = {
fileNames: [],
errors: [],
options: {
jsx: ts.JsxEmit.Preserve,
declaration: true,
emitDeclarationOnly: true,
skipLibCheck: true,
},
};
// 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.
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 = {
getCompilationSettings: (): ts.CompilerOptions => parsedCommandLine.options,
getScriptFileNames: (): string[] => parsedCommandLine.fileNames,
getScriptVersion: (path: string): string => {
return getFileContent(path).version.toString();
},
getScriptSnapshot: (path: string): ts.IScriptSnapshot | undefined => {
// if (!ts.sys.fileExists(fileName)) {
const text = getFileContent(path).text;
return {
getText: (start: number, end: number) => {
if (start === 0 && end === text.length) {
// optimization
return text;
} else {
return text.slice(start, end);
}
},
getLength: () => text.length,
getChangeRange: (
oldSnapshot: ts.IScriptSnapshot
): ts.TextChangeRange | undefined => {
return undefined;
},
};
},
getCurrentDirectory: ts.sys.getCurrentDirectory,
getDefaultLibFileName: ts.getDefaultLibFilePath,
};
const languageService = ts.createLanguageService(languageServiceHost);
function compile(tsPath: string, tsLibs: string[]) {
parsedCommandLine.fileNames = [tsPath, ...tsLibs];
const program = languageService.getProgram()!;
const tsHost = ts.createCompilerHost(parsedCommandLine.options);
const createdFiles = {};
tsHost.writeFile = (fileName, contents) => (createdFiles[fileName] = contents);
program.emit(undefined /* all files */, tsHost.writeFile);
return createdFiles[parsedCommandLine.fileNames[0].replace(".tsx", ".d.ts")];
}
2021-04-22 18:33:42 +02:00
function writeFile(file, data): Promise<void> {
return new Promise((resolve, reject) => {
fs.writeFile(file, data, (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, "utf8", (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
async function writeDts(tsPath, dtsPath, tsLibs) {
const dtsSource = compile(tsPath, tsLibs);
await writeFile(dtsPath, dtsSource);
}
2021-04-22 18:33:42 +02:00
function writeTs(svelteSource, sveltePath, tsPath): void {
let tsSource = svelte2tsx(svelteSource, {
filename: sveltePath,
isTsFile: true,
mode: "dts",
});
let codeLines = tsSource.code.split("\n");
updateFileContent(tsPath, codeLines.join("\n"));
}
async function writeJs(
source: string,
inputFilename: string,
outputJsPath: string,
outputCssPath: string,
binDir: string,
genDir: string
): Promise<void> {
const preprocessOptions = preprocess({
scss: {
includePaths: [
binDir,
genDir,
// a nasty hack to ensure sass/... resolves correctly
// when invoked from an external workspace
`${binDir}/external/ankidesktop`,
`${genDir}/external/ankidesktop`,
`${binDir}/../../../external/ankidesktop`,
],
},
});
try {
const processed = await svelte.preprocess(source, preprocessOptions, {
filename: inputFilename,
});
const result = svelte.compile(processed.toString!(), {
format: "esm",
css: false,
generate: "dom",
filename: outputJsPath,
});
// warnings are an error
if (result.warnings.length > 0) {
console.log(`warnings during compile: ${result.warnings}`);
}
// write out the css file
const outputCss = result.css.code ?? "";
await writeFile(outputCssPath, outputCss);
// if it was non-empty, prepend a reference to it in the js file, so that
// it's included in the bundled .css when bundling
const outputSource =
(outputCss ? `import "./${basename(outputCssPath)}";` : "") +
result.js.code;
await writeFile(outputJsPath, outputSource);
} catch (err) {
console.log(`compile failed: ${err}`);
return;
}
}
async function compileSvelte(args) {
const [sveltePath, mjsPath, dtsPath, cssPath, binDir, genDir, ...tsLibs] = args;
const svelteSource = (await readFile(sveltePath)) as string;
const mockTsPath = sveltePath + ".tsx";
2021-04-22 18:33:42 +02:00
writeTs(svelteSource, sveltePath, mockTsPath);
await writeDts(mockTsPath, dtsPath, tsLibs);
await writeJs(svelteSource, sveltePath, mjsPath, cssPath, binDir, genDir);
return true;
}
function main() {
if (worker.runAsWorker(process.argv)) {
console.log = worker.log;
worker.log("Svelte running as a Bazel worker");
worker.runWorkerLoop(compileSvelte);
} else {
const paramFile = process.argv[2].replace(/^@/, "");
const commandLineArgs = fs.readFileSync(paramFile, "utf-8").trim().split("\n");
console.log("Svelte running as a standalone process");
compileSvelte(commandLineArgs);
}
}
if (require.main === module) {
main();
process.exitCode = 0;
}