mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
Svelte build improvements
1. All Svelte files in a package are compiled in one step now, which ensures that properties that use types from a different Svelte file in the same package are typed correctly. The single-file svelte() has been removed, and compile_svelte() may be renamed to svelte() in the future. 2. The .ts files in the same package are included as part of the Svelte compilation, so that types imported imported from .ts files in the same package work. 3. Dependencies passed into the rule are now loaded into the TypeScript compiler, so that properties referencing types from different packages work. We'll need to update our compile_svelte() lines to list the dependencies. For example, before this change: % cat bazel-bin/ts/congrats/CongratsPage.svelte.d.ts import { SvelteComponentTyped } from "svelte"; declare const __propDef: { props: { info: any; }; ... After adding //ts/lib to the deps of compile_svelte() in ts/congrats: % cat bazel-bin/ts/congrats/CongratsPage.svelte.d.ts import { SvelteComponentTyped } from "svelte"; import type { Scheduler } from "../lib/proto"; declare const __propDef: { props: { info: Scheduler.CongratsInfoResponse; }; ...
This commit is contained in:
parent
a29bd7c9f0
commit
1f876cfe39
3 changed files with 198 additions and 93 deletions
|
@ -1,7 +1,7 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
load("//ts:prettier.bzl", "prettier_test")
|
||||
load("//ts:eslint.bzl", "eslint_test")
|
||||
load("//ts/svelte:svelte.bzl", "svelte", "svelte_check")
|
||||
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
|
||||
load("//ts:esbuild.bzl", "esbuild")
|
||||
load("//ts:compile_sass.bzl", "compile_sass")
|
||||
load("//ts:typescript.bzl", "typescript")
|
||||
|
@ -16,15 +16,14 @@ compile_sass(
|
|||
],
|
||||
)
|
||||
|
||||
svelte(
|
||||
name = "CongratsPage",
|
||||
entry_point = "CongratsPage.svelte",
|
||||
compile_svelte(
|
||||
deps = ["//ts/lib"],
|
||||
)
|
||||
|
||||
typescript(
|
||||
name = "index",
|
||||
deps = [
|
||||
"CongratsPage",
|
||||
":svelte",
|
||||
"//ts/lib",
|
||||
"@npm//@fluent",
|
||||
"@npm//svelte",
|
||||
|
@ -41,9 +40,9 @@ esbuild(
|
|||
output_css = "congrats.css",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"CongratsPage",
|
||||
":base_css",
|
||||
":index",
|
||||
":svelte",
|
||||
"//ts/lib",
|
||||
"@npm//protobufjs",
|
||||
],
|
||||
|
|
|
@ -2,82 +2,97 @@ load("@npm//svelte-check:index.bzl", _svelte_check = "svelte_check_test")
|
|||
load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "declaration_info")
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "SassInfo")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
|
||||
load("@bazel_skylib//lib:paths.bzl", "paths")
|
||||
|
||||
def _get_dep_sources(dep):
|
||||
def _get_declarations(dep):
|
||||
if SassInfo in dep:
|
||||
return dep[SassInfo].transitive_sources
|
||||
elif DeclarationInfo in dep:
|
||||
return dep[DeclarationInfo].transitive_declarations
|
||||
else:
|
||||
return []
|
||||
|
||||
def _get_sources(deps):
|
||||
return depset([], transitive = [_get_dep_sources(dep) for dep in deps])
|
||||
fail("unexpected dep", dep)
|
||||
|
||||
def _svelte(ctx):
|
||||
args = ctx.actions.args()
|
||||
args.use_param_file("@%s", use_always = True)
|
||||
args.set_param_file_format("multiline")
|
||||
|
||||
args.add(ctx.file.entry_point.path)
|
||||
args.add(ctx.outputs.mjs.path)
|
||||
args.add(ctx.outputs.dts.path)
|
||||
args.add(ctx.outputs.css.path)
|
||||
# path to bin folder, for sass
|
||||
args.add(ctx.var["BINDIR"])
|
||||
args.add(ctx.var["GENDIR"])
|
||||
args.add_all(ctx.files._shims)
|
||||
|
||||
deps = _get_sources(ctx.attr.deps).to_list()
|
||||
# svelte and ts sources
|
||||
outputs = []
|
||||
dts_only = []
|
||||
nondts_only = []
|
||||
for src in ctx.files.srcs:
|
||||
args.add(src.path)
|
||||
|
||||
if src.path.endswith(".svelte"):
|
||||
# strip off external/ankidesktop if invoked from external workspace
|
||||
path = src.path
|
||||
if src.path.startswith("external/ankidesktop/"):
|
||||
path = path[len("external/ankidesktop/"):]
|
||||
|
||||
# strip off package prefix, eg ts/editor/mathjax/Foo.svelte -> mathjax/Foo.svelte
|
||||
base = path[len(ctx.label.package) + 1:]
|
||||
for ext in ("d.ts", "css", "mjs"):
|
||||
out = ctx.actions.declare_file(base.replace(".svelte", ".svelte." + ext))
|
||||
args.add(out)
|
||||
outputs.append(out)
|
||||
if ext == "d.ts":
|
||||
dts_only.append(out)
|
||||
else:
|
||||
nondts_only.append(out)
|
||||
|
||||
# dependencies
|
||||
deps = depset([], transitive = [_get_declarations(dep) for dep in ctx.attr.deps])
|
||||
args.add_all(deps)
|
||||
|
||||
ctx.actions.run(
|
||||
execution_requirements = {"supports-workers": "1"},
|
||||
executable = ctx.executable._svelte_bin,
|
||||
outputs = [ctx.outputs.mjs, ctx.outputs.dts, ctx.outputs.css],
|
||||
inputs = [ctx.file.entry_point] + deps + ctx.files._shims,
|
||||
outputs = outputs,
|
||||
inputs = ctx.files.srcs + deps.to_list(),
|
||||
mnemonic = "Svelte",
|
||||
progress_message = "Compiling Svelte {}:{}".format(ctx.label.package, ctx.attr.name),
|
||||
arguments = [args],
|
||||
)
|
||||
|
||||
return [
|
||||
declaration_info(depset([ctx.outputs.dts]), deps = [ctx.attr._shims]),
|
||||
declaration_info(depset(dts_only), deps = ctx.attr.deps),
|
||||
DefaultInfo(
|
||||
files = depset(nondts_only),
|
||||
runfiles = ctx.runfiles(files = outputs, transitive_files = deps),
|
||||
),
|
||||
]
|
||||
|
||||
svelte = rule(
|
||||
implementation = _svelte,
|
||||
attrs = {
|
||||
"entry_point": attr.label(allow_single_file = True),
|
||||
"srcs": attr.label_list(allow_files = True, doc = ".ts and .svelte files"),
|
||||
"deps": attr.label_list(),
|
||||
"_svelte_bin": attr.label(
|
||||
default = Label("//ts/svelte:svelte_bin"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
"_shims": attr.label(
|
||||
default = Label("@npm//svelte2tsx:svelte2tsx__typings"),
|
||||
allow_files = True,
|
||||
),
|
||||
},
|
||||
outputs = {
|
||||
"mjs": "%{name}.svelte.mjs",
|
||||
"dts": "%{name}.svelte.d.ts",
|
||||
"css": "%{name}.svelte.css",
|
||||
},
|
||||
)
|
||||
|
||||
def compile_svelte(name = "svelte", srcs = None, deps = [], visibility = ["//visibility:private"]):
|
||||
if not srcs:
|
||||
srcs = native.glob(["*.svelte"])
|
||||
for src in srcs:
|
||||
svelte(
|
||||
name = src.replace(".svelte", ""),
|
||||
entry_point = src,
|
||||
deps = deps,
|
||||
visibility = visibility,
|
||||
)
|
||||
srcs = native.glob([
|
||||
"**/*.svelte",
|
||||
"**/*.ts",
|
||||
])
|
||||
|
||||
js_library(
|
||||
svelte(
|
||||
name = name,
|
||||
srcs = [s.replace(".svelte", "") for s in srcs],
|
||||
srcs = srcs,
|
||||
deps = deps + [
|
||||
"@npm//svelte2tsx",
|
||||
],
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
|
|
|
@ -10,21 +10,20 @@ import { basename } from "path";
|
|||
import * as ts from "typescript";
|
||||
import * as svelte from "svelte/compiler.js";
|
||||
|
||||
let parsedCommandLine: ts.ParsedCommandLine = {
|
||||
const parsedCommandLine: ts.ParsedCommandLine = {
|
||||
fileNames: [],
|
||||
errors: [],
|
||||
options: {
|
||||
jsx: ts.JsxEmit.Preserve,
|
||||
declaration: true,
|
||||
emitDeclarationOnly: true,
|
||||
skipLibCheck: true,
|
||||
// noEmitOnError: true,
|
||||
paths: {
|
||||
"*": ["*", "external/npm/node_modules/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
@ -43,17 +42,17 @@ function getFileContent(path: string): FileContent {
|
|||
return content;
|
||||
}
|
||||
|
||||
function updateFileContent(path: string, text: string): void {
|
||||
let content = fileContent.get(path);
|
||||
function updateFileContent(input: InputFile): void {
|
||||
let content = fileContent.get(input.path);
|
||||
if (content) {
|
||||
content.text = text;
|
||||
content.text = input.data;
|
||||
content.version += 1;
|
||||
} else {
|
||||
content = {
|
||||
text,
|
||||
text: input.data,
|
||||
version: 0,
|
||||
};
|
||||
fileContent.set(path, content);
|
||||
fileContent.set(input.path, content);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +64,6 @@ const languageServiceHost: ts.LanguageServiceHost = {
|
|||
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) => {
|
||||
|
@ -78,7 +76,7 @@ const languageServiceHost: ts.LanguageServiceHost = {
|
|||
},
|
||||
getLength: () => text.length,
|
||||
getChangeRange: (
|
||||
oldSnapshot: ts.IScriptSnapshot
|
||||
_oldSnapshot: ts.IScriptSnapshot
|
||||
): ts.TextChangeRange | undefined => {
|
||||
return undefined;
|
||||
},
|
||||
|
@ -90,14 +88,29 @@ const languageServiceHost: ts.LanguageServiceHost = {
|
|||
|
||||
const languageService = ts.createLanguageService(languageServiceHost);
|
||||
|
||||
function compile(tsPath: string, tsLibs: string[]) {
|
||||
parsedCommandLine.fileNames = [tsPath, ...tsLibs];
|
||||
async function emitTypings(svelte: SvelteTsxFile[], deps: InputFile[]): Promise<void> {
|
||||
const allFiles = [...svelte, ...deps];
|
||||
allFiles.forEach(updateFileContent);
|
||||
parsedCommandLine.fileNames = allFiles.map((i) => i.path);
|
||||
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")];
|
||||
const cwd = ts.sys.getCurrentDirectory().replace(/\\/g, "/");
|
||||
tsHost.writeFile = (fileName, contents) => {
|
||||
// tsc makes some paths absolute for some reason
|
||||
if (fileName.startsWith(cwd)) {
|
||||
fileName = fileName.substring(cwd.length + 1);
|
||||
}
|
||||
createdFiles[fileName] = contents;
|
||||
};
|
||||
const result = program.emit(undefined /* all files */, tsHost.writeFile);
|
||||
// for (const diag of result.diagnostics) {
|
||||
// console.log(diag.messageText);
|
||||
// }
|
||||
|
||||
for (const file of svelte) {
|
||||
await writeFile(file.realDtsPath, createdFiles[file.virtualDtsPath]);
|
||||
}
|
||||
}
|
||||
|
||||
function writeFile(file, data): Promise<void> {
|
||||
|
@ -124,26 +137,8 @@ function readFile(file) {
|
|||
});
|
||||
}
|
||||
|
||||
async function writeDts(tsPath, dtsPath, tsLibs) {
|
||||
const dtsSource = compile(tsPath, tsLibs);
|
||||
await writeFile(dtsPath, dtsSource);
|
||||
}
|
||||
|
||||
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,
|
||||
async function compileSingleSvelte(
|
||||
input: SvelteInput,
|
||||
binDir: string,
|
||||
genDir: string
|
||||
): Promise<void> {
|
||||
|
@ -162,14 +157,14 @@ async function writeJs(
|
|||
});
|
||||
|
||||
try {
|
||||
const processed = await svelte.preprocess(source, preprocessOptions, {
|
||||
filename: inputFilename,
|
||||
const processed = await svelte.preprocess(input.data, preprocessOptions, {
|
||||
filename: input.path,
|
||||
});
|
||||
const result = svelte.compile(processed.toString!(), {
|
||||
format: "esm",
|
||||
css: false,
|
||||
generate: "dom",
|
||||
filename: outputJsPath,
|
||||
filename: input.mjsPath,
|
||||
});
|
||||
// warnings are an error
|
||||
if (result.warnings.length > 0) {
|
||||
|
@ -177,27 +172,123 @@ async function writeJs(
|
|||
}
|
||||
// write out the css file
|
||||
const outputCss = result.css.code ?? "";
|
||||
await writeFile(outputCssPath, outputCss);
|
||||
await writeFile(input.cssPath, 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)}";` : "") +
|
||||
(outputCss ? `import "./${basename(input.cssPath)}";` : "") +
|
||||
result.js.code;
|
||||
await writeFile(outputJsPath, outputSource);
|
||||
await writeFile(input.mjsPath, 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;
|
||||
interface Args {
|
||||
binDir: string;
|
||||
genDir: string;
|
||||
svelteFiles: SvelteInput[];
|
||||
dependencies: InputFile[];
|
||||
}
|
||||
|
||||
const mockTsPath = sveltePath + ".tsx";
|
||||
writeTs(svelteSource, sveltePath, mockTsPath);
|
||||
await writeDts(mockTsPath, dtsPath, tsLibs);
|
||||
await writeJs(svelteSource, sveltePath, mjsPath, cssPath, binDir, genDir);
|
||||
interface InputFile {
|
||||
path: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface SvelteInput extends InputFile {
|
||||
dtsPath: string;
|
||||
cssPath: string;
|
||||
mjsPath: string;
|
||||
}
|
||||
|
||||
async function extractArgsAndData(args: string[]): Promise<Args> {
|
||||
const [binDir, genDir, ...rest] = args;
|
||||
const [svelteFiles, dependencies] = await extractSvelteAndDeps(rest);
|
||||
return {
|
||||
binDir,
|
||||
genDir,
|
||||
svelteFiles,
|
||||
dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
async function extractSvelteAndDeps(
|
||||
files: string[]
|
||||
): Promise<[SvelteInput[], InputFile[]]> {
|
||||
const svelte: SvelteInput[] = [];
|
||||
const deps: InputFile[] = [];
|
||||
files.reverse();
|
||||
while (files.length) {
|
||||
const file = files.pop()!;
|
||||
const data = (await readFile(file)) as string;
|
||||
if (file.endsWith(".svelte")) {
|
||||
svelte.push({
|
||||
path: file,
|
||||
data,
|
||||
dtsPath: files.pop()!,
|
||||
cssPath: files.pop()!,
|
||||
mjsPath: files.pop()!,
|
||||
});
|
||||
} else {
|
||||
deps.push({ path: remapBinToSrcDir(file), data });
|
||||
}
|
||||
}
|
||||
return [svelte, deps];
|
||||
}
|
||||
|
||||
/// Our generated .tsx files sit in the bin dir, but .ts files
|
||||
/// may be coming from the source folder, which breaks ./foo imports.
|
||||
/// Adjust the path to make it appear they're all in the same folder.
|
||||
function remapBinToSrcDir(file: string): string {
|
||||
return file.replace(new RegExp("bazel-out/[-_a-z]+/bin/"), "");
|
||||
}
|
||||
|
||||
/// Generate Svelte .mjs/.css files.
|
||||
async function compileSvelte(
|
||||
svelte: SvelteInput[],
|
||||
binDir: string,
|
||||
genDir: string
|
||||
): Promise<void> {
|
||||
for (const file of svelte) {
|
||||
await compileSingleSvelte(file, binDir, genDir);
|
||||
}
|
||||
}
|
||||
|
||||
interface SvelteTsxFile extends InputFile {
|
||||
// relative to src folder
|
||||
virtualDtsPath: string;
|
||||
// must go to bazel-out
|
||||
realDtsPath: string;
|
||||
}
|
||||
|
||||
function generateTsxFiles(svelteFiles: SvelteInput[]): SvelteTsxFile[] {
|
||||
return svelteFiles.map((file) => {
|
||||
const data = svelte2tsx(file.data, {
|
||||
filename: file.path,
|
||||
isTsFile: true,
|
||||
mode: "dts",
|
||||
}).code;
|
||||
const path = file.path.replace(".svelte", ".svelte.tsx");
|
||||
return {
|
||||
path,
|
||||
data,
|
||||
virtualDtsPath: path.replace(".tsx", ".d.ts"),
|
||||
realDtsPath: file.dtsPath,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function compileSvelteAndGenerateTypings(argsList: string[]): Promise<boolean> {
|
||||
const args = await extractArgsAndData(argsList);
|
||||
|
||||
// mjs/css
|
||||
await compileSvelte(args.svelteFiles, args.binDir, args.genDir);
|
||||
|
||||
// d.ts
|
||||
const tsxFiles = generateTsxFiles(args.svelteFiles);
|
||||
await emitTypings(tsxFiles, args.dependencies);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -206,12 +297,12 @@ function main() {
|
|||
if (worker.runAsWorker(process.argv)) {
|
||||
console.log = worker.log;
|
||||
worker.log("Svelte running as a Bazel worker");
|
||||
worker.runWorkerLoop(compileSvelte);
|
||||
worker.runWorkerLoop(compileSvelteAndGenerateTypings);
|
||||
} 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);
|
||||
compileSvelteAndGenerateTypings(commandLineArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue