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.
|
|
|
|
|
2021-03-20 05:56:08 +01:00
|
|
|
const fs = require("fs");
|
2021-03-20 11:42:29 +01:00
|
|
|
const worker = require("@bazel/worker");
|
|
|
|
const svelte2tsx = require("svelte2tsx");
|
|
|
|
const preprocess = require("svelte-preprocess");
|
2021-03-21 06:45:15 +01:00
|
|
|
import { basename } from "path";
|
2021-03-20 14:13:27 +01:00
|
|
|
import * as ts from "typescript";
|
2021-03-21 05:30:01 +01:00
|
|
|
import * as svelte from "svelte/compiler.js";
|
2021-03-20 05:56:08 +01:00
|
|
|
|
2021-03-20 14:49:10 +01:00
|
|
|
let parsedCommandLine: ts.ParsedCommandLine = {
|
|
|
|
fileNames: [],
|
|
|
|
errors: [],
|
|
|
|
options: {
|
|
|
|
jsx: ts.JsxEmit.Preserve,
|
|
|
|
declaration: true,
|
|
|
|
emitDeclarationOnly: true,
|
|
|
|
skipLibCheck: true,
|
|
|
|
},
|
2021-03-20 11:42:29 +01:00
|
|
|
};
|
2021-03-20 14:49:10 +01:00
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
// 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.
|
2021-03-20 15:17:09 +01:00
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
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)
|
2021-03-20 14:49:10 +01:00
|
|
|
const languageServiceHost: ts.LanguageServiceHost = {
|
|
|
|
getCompilationSettings: (): ts.CompilerOptions => parsedCommandLine.options,
|
|
|
|
getScriptFileNames: (): string[] => parsedCommandLine.fileNames,
|
2021-03-21 05:12:00 +01:00
|
|
|
getScriptVersion: (path: string): string => {
|
|
|
|
return getFileContent(path).version.toString();
|
2021-03-20 14:49:10 +01:00
|
|
|
},
|
2021-03-21 05:12:00 +01:00
|
|
|
getScriptSnapshot: (path: string): ts.IScriptSnapshot | undefined => {
|
|
|
|
// if (!ts.sys.fileExists(fileName)) {
|
|
|
|
const text = getFileContent(path).text;
|
2021-03-20 14:49:10 +01:00
|
|
|
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 => {
|
2021-03-21 05:12:00 +01:00
|
|
|
return undefined;
|
2021-03-20 14:49:10 +01:00
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
|
|
getDefaultLibFileName: ts.getDefaultLibFilePath,
|
|
|
|
};
|
|
|
|
|
|
|
|
const languageService = ts.createLanguageService(languageServiceHost);
|
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
function compile(tsPath: string, tsLibs: string[]) {
|
|
|
|
parsedCommandLine.fileNames = [tsPath, ...tsLibs];
|
2021-03-20 14:49:10 +01:00
|
|
|
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);
|
2021-05-04 10:53:36 +02:00
|
|
|
return createdFiles[parsedCommandLine.fileNames[0].replace(".tsx", ".d.ts")];
|
2021-03-20 14:49:10 +01:00
|
|
|
}
|
2021-03-20 05:56:08 +01:00
|
|
|
|
2021-04-22 18:33:42 +02:00
|
|
|
function writeFile(file, data): Promise<void> {
|
2021-03-20 11:42:29 +01:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
fs.writeFile(file, data, (err) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-03-20 05:56:08 +01:00
|
|
|
|
2021-03-20 11:42:29 +01:00
|
|
|
function readFile(file) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
fs.readFile(file, "utf8", (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resolve(data);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-03-20 05:56:08 +01:00
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
async function writeDts(tsPath, dtsPath, tsLibs) {
|
|
|
|
const dtsSource = compile(tsPath, tsLibs);
|
2021-03-20 11:42:29 +01:00
|
|
|
await writeFile(dtsPath, dtsSource);
|
|
|
|
}
|
|
|
|
|
2021-04-22 18:33:42 +02:00
|
|
|
function writeTs(svelteSource, sveltePath, tsPath): void {
|
2021-03-20 11:42:29 +01:00
|
|
|
let tsSource = svelte2tsx(svelteSource, {
|
|
|
|
filename: sveltePath,
|
|
|
|
strictMode: true,
|
|
|
|
isTsFile: true,
|
|
|
|
});
|
|
|
|
let codeLines = tsSource.code.split("\n");
|
|
|
|
// replace the "///<reference types="svelte" />" with a line
|
|
|
|
// turning off checking, as we'll use svelte-check for that
|
|
|
|
codeLines[0] = "// @ts-nocheck";
|
2021-03-21 05:12:00 +01:00
|
|
|
updateFileContent(tsPath, codeLines.join("\n"));
|
2021-03-20 11:42:29 +01:00
|
|
|
}
|
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
async function writeJs(
|
|
|
|
source: string,
|
|
|
|
inputFilename: string,
|
|
|
|
outputJsPath: string,
|
2021-04-15 06:27:53 +02:00
|
|
|
outputCssPath: string,
|
|
|
|
binDir: string,
|
|
|
|
genDir: string
|
2021-03-21 05:12:00 +01:00
|
|
|
): Promise<void> {
|
2021-04-15 06:27:53 +02:00
|
|
|
const preprocessOptions = preprocess({
|
|
|
|
scss: { includePaths: [binDir, genDir] },
|
|
|
|
});
|
2021-03-20 11:42:29 +01:00
|
|
|
preprocessOptions.filename = inputFilename;
|
2021-03-20 05:56:08 +01:00
|
|
|
|
|
|
|
try {
|
2021-03-20 11:42:29 +01:00
|
|
|
const processed = await svelte.preprocess(source, preprocessOptions);
|
2021-03-20 14:13:27 +01:00
|
|
|
const result = svelte.compile(processed.toString!(), {
|
2021-03-20 11:42:29 +01:00
|
|
|
format: "esm",
|
2021-03-21 06:45:15 +01:00
|
|
|
css: false,
|
2021-03-20 11:42:29 +01:00
|
|
|
generate: "dom",
|
2021-03-21 05:12:00 +01:00
|
|
|
filename: outputJsPath,
|
2021-03-20 11:42:29 +01:00
|
|
|
});
|
|
|
|
// warnings are an error
|
|
|
|
if (result.warnings.length > 0) {
|
|
|
|
console.log(`warnings during compile: ${result.warnings}`);
|
|
|
|
}
|
2021-03-21 06:45:15 +01:00
|
|
|
// write out the css file
|
2021-03-21 05:23:40 +01:00
|
|
|
const outputCss = result.css.code ?? "";
|
|
|
|
await writeFile(outputCssPath, outputCss);
|
2021-03-21 06:45:15 +01:00
|
|
|
// 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);
|
2021-03-20 05:56:08 +01:00
|
|
|
} catch (err) {
|
2021-03-20 11:42:29 +01:00
|
|
|
console.log(`compile failed: ${err}`);
|
|
|
|
return;
|
2021-03-20 05:56:08 +01:00
|
|
|
}
|
2021-03-20 11:42:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function compileSvelte(args) {
|
2021-04-15 06:27:53 +02:00
|
|
|
const [sveltePath, mjsPath, dtsPath, cssPath, binDir, genDir, ...tsLibs] = args;
|
2021-03-21 05:12:00 +01:00
|
|
|
const svelteSource = (await readFile(sveltePath)) as string;
|
2021-03-20 15:17:09 +01:00
|
|
|
|
2021-05-04 10:53:36 +02:00
|
|
|
const mockTsPath = sveltePath + ".tsx";
|
2021-04-22 18:33:42 +02:00
|
|
|
writeTs(svelteSource, sveltePath, mockTsPath);
|
2021-03-21 05:12:00 +01:00
|
|
|
await writeDts(mockTsPath, dtsPath, tsLibs);
|
2021-04-15 06:27:53 +02:00
|
|
|
await writeJs(svelteSource, sveltePath, mjsPath, cssPath, binDir, genDir);
|
2021-03-20 11:42:29 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-03-21 05:12:00 +01:00
|
|
|
function main() {
|
2021-03-20 11:42:29 +01:00
|
|
|
if (worker.runAsWorker(process.argv)) {
|
2021-03-21 05:12:00 +01:00
|
|
|
console.log = worker.log;
|
2021-03-20 11:42:29 +01:00
|
|
|
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);
|
2021-03-20 05:56:08 +01:00
|
|
|
}
|
2021-03-20 11:42:29 +01:00
|
|
|
}
|
2021-03-20 05:56:08 +01:00
|
|
|
|
2021-03-20 11:42:29 +01:00
|
|
|
if (require.main === module) {
|
2021-03-21 05:12:00 +01:00
|
|
|
main();
|
2021-03-20 11:42:29 +01:00
|
|
|
process.exitCode = 0;
|
|
|
|
}
|