/
emitUtils.ts
101 lines (89 loc) · 3.59 KB
/
emitUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import fs from 'fs-extra';
import {createHash} from 'crypto';
import {findAsyncSequential} from './jsUtils';
const fileHash = new Map<string, string>();
/**
* Outputs a file to the generated files directory. Only writes files if content
* differs from cache (for hot reload performance).
*
* @param generatedFilesDir Absolute path.
* @param file Path relative to `generatedFilesDir`. File will always be
* outputted; no need to ensure directory exists.
* @param content String content to write.
* @param skipCache If `true` (defaults as `true` for production), file is
* force-rewritten, skipping cache.
*/
export async function generate(
generatedFilesDir: string,
file: string,
content: string,
skipCache: boolean = process.env.NODE_ENV === 'production',
): Promise<void> {
const filepath = path.resolve(generatedFilesDir, file);
if (skipCache) {
await fs.outputFile(filepath, content);
// Cache still needs to be reset, otherwise, writing "A", "B", and "A" where
// "B" skips cache will cause the last "A" not be able to overwrite as the
// first "A" remains in cache. But if the file never existed in cache, no
// need to register it.
if (fileHash.get(filepath)) {
fileHash.set(filepath, createHash('md5').update(content).digest('hex'));
}
return;
}
let lastHash = fileHash.get(filepath);
// If file already exists but it's not in runtime cache yet, we try to
// calculate the content hash and then compare. This is to avoid unnecessary
// overwriting and we can reuse old file.
if (!lastHash && (await fs.pathExists(filepath))) {
const lastContent = await fs.readFile(filepath, 'utf8');
lastHash = createHash('md5').update(lastContent).digest('hex');
fileHash.set(filepath, lastHash);
}
const currentHash = createHash('md5').update(content).digest('hex');
if (lastHash !== currentHash) {
await fs.outputFile(filepath, content);
fileHash.set(filepath, currentHash);
}
}
/**
* @param permalink The URL that the HTML file corresponds to, without base URL
* @param outDir Full path to the output directory
* @param trailingSlash The site config option. If provided, only one path will
* be read.
* @returns This returns a buffer, which you have to decode string yourself if
* needed. (Not always necessary since the output isn't for human consumption
* anyways, and most HTML manipulation libs accept buffers)
* @throws Throws when the HTML file is not found at any of the potential paths.
* This should never happen as it would lead to a 404.
*/
export async function readOutputHTMLFile(
permalink: string,
outDir: string,
trailingSlash: boolean | undefined,
): Promise<Buffer> {
const withTrailingSlashPath = path.join(outDir, permalink, 'index.html');
const withoutTrailingSlashPath = (() => {
const basePath = path.join(outDir, permalink.replace(/\/$/, ''));
const htmlSuffix = /\.html?$/i.test(basePath) ? '' : '.html';
return `${basePath}${htmlSuffix}`;
})();
const possibleHtmlPaths = [
trailingSlash !== false && withTrailingSlashPath,
trailingSlash !== true && withoutTrailingSlashPath,
].filter((p): p is string => Boolean(p));
const HTMLPath = await findAsyncSequential(possibleHtmlPaths, fs.pathExists);
if (!HTMLPath) {
throw new Error(
`Expected output HTML file to be found at ${withTrailingSlashPath} for permalink ${permalink}.`,
);
}
return fs.readFile(HTMLPath);
}