Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/project/serve/render.ts
6458 views
1
import { isAbsolute, join } from "../../deno_ral/path.ts";
2
3
import { RenderResult, RenderResultFile } from "../../command/render/types.ts";
4
import { md5HashSync } from "../../core/hash.ts";
5
import { HttpDevServerRenderMonitor } from "../../core/http-devserver.ts";
6
import { isJupyterNotebook } from "../../core/jupyter/jupyter.ts";
7
import { logError } from "../../core/log.ts";
8
import { isHtmlContent } from "../../core/mime.ts";
9
import { PromiseQueue } from "../../core/promise.ts";
10
import { extensionFilesFromDirs } from "../../extension/extension.ts";
11
import { projectFormatOutputDir } from "../project-shared.ts";
12
import { kProjectType, ProjectContext } from "../types.ts";
13
import { projectType } from "../types/project-types.ts";
14
15
export class ServeRenderManager {
16
public fileRequiresReRender(
17
file: string,
18
inputFile: string,
19
extensionDirs: string[],
20
resourceFiles: string[],
21
project: ProjectContext,
22
) {
23
const lastRenderHash = this.fileRenders_.get(file);
24
if (lastRenderHash) {
25
return lastRenderHash !==
26
this.fileRenderHash(
27
file,
28
inputFile,
29
extensionDirs,
30
resourceFiles,
31
project,
32
);
33
} else {
34
return true;
35
}
36
}
37
38
public submitRender(render: () => Promise<RenderResult>) {
39
HttpDevServerRenderMonitor.onRenderStart();
40
return this.renderQueue_.enqueue(render);
41
}
42
43
public isRendering() {
44
return this.renderQueue_.isRunning();
45
}
46
47
public onRenderError(error: Error) {
48
HttpDevServerRenderMonitor.onRenderStop(false);
49
if (error.message) {
50
logError(error);
51
}
52
}
53
54
public onRenderResult(
55
result: RenderResult,
56
extensionDirs: string[],
57
resourceFiles: string[],
58
project: ProjectContext,
59
) {
60
HttpDevServerRenderMonitor.onRenderStop(true);
61
const fileOutputDir = (file: RenderResultFile) => {
62
const format = file.format;
63
return projectFormatOutputDir(
64
format,
65
project,
66
projectType(project.config?.project?.[kProjectType]),
67
);
68
};
69
70
result.files.forEach((resultFile) => {
71
const file = isAbsolute(resultFile.file)
72
? resultFile.file
73
: join(fileOutputDir(resultFile), resultFile.file);
74
const inputFile = isAbsolute(resultFile.input)
75
? resultFile.input
76
: join(project.dir, resultFile.input);
77
this.fileRenders_.set(
78
file,
79
this.fileRenderHash(
80
file,
81
inputFile,
82
extensionDirs,
83
resourceFiles,
84
project,
85
),
86
);
87
});
88
}
89
90
private fileRenderHash(
91
file: string,
92
inputFile: string,
93
extensionDirs: string[],
94
resourceFiles: string[],
95
project: ProjectContext,
96
) {
97
const resourceHash = [
98
file,
99
inputFile,
100
...resourceFiles,
101
...extensionFilesFromDirs(extensionDirs),
102
...(project.files.config || []),
103
...(project.files.configResources || []),
104
...(project.files.resources || []),
105
].reduce((hash, file) => {
106
try {
107
return hash + Deno.statSync(file).mtime?.toUTCString();
108
} catch {
109
return hash;
110
}
111
}, "");
112
// very large jupyter notebooks can take a long time to hash
113
// (~ 2 seconds for every 10mb) so we use the slightly less
114
// robust file modification time in that case. non-html
115
// content also shouldn't be read with readTextFileSync
116
if (isJupyterNotebook(inputFile) || !isHtmlContent(file)) {
117
return String(Deno.statSync(file).mtime) +
118
String(Deno.statSync(inputFile).mtime) +
119
resourceHash;
120
} else {
121
return md5HashSync(Deno.readTextFileSync(file)) +
122
md5HashSync(Deno.readTextFileSync(inputFile)) +
123
resourceHash;
124
}
125
}
126
127
private fileRenders_ = new Map<string, string>();
128
private renderQueue_ = new PromiseQueue<RenderResult>();
129
}
130
131