@abreen/tada 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -7
- package/bin/tada.ts +23 -9
- package/bin/validators.ts +34 -1
- package/build/branding.ts +41 -0
- package/build/build-manifest.ts +10 -5
- package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
- package/build/build-validation.ts +176 -0
- package/build/bundle.ts +75 -65
- package/build/columns-plugin.ts +27 -1
- package/build/config-loader.ts +3 -3
- package/build/custom-fonts.ts +8 -5
- package/build/deflist-id-plugin.ts +2 -2
- package/build/generate-favicon.ts +7 -6
- package/build/generate-fonts.ts +8 -15
- package/build/generate-katex-assets.ts +19 -17
- package/build/generate-web-app-manifest.ts +3 -11
- package/build/lodash-template.ts +14 -0
- package/build/output-publication.ts +238 -0
- package/build/pagefind.ts +49 -21
- package/build/pdf-text.ts +41 -1
- package/build/pipeline.ts +95 -101
- package/build/site-assets.ts +46 -0
- package/build/site-build.ts +267 -0
- package/build/source-model.ts +223 -229
- package/build/source-records.ts +69 -81
- package/build/template-globals.ts +3 -0
- package/build/templates.ts +12 -8
- package/build/toc-plugin.ts +21 -21
- package/build/types.d.ts +38 -38
- package/build/util.ts +0 -4
- package/build/utils/code.ts +51 -117
- package/build/utils/final-html.ts +171 -47
- package/build/utils/jdi-runner/LiterateRunner.java +9 -1
- package/build/utils/literate-java.ts +11 -7
- package/build/utils/markdown-partials.ts +4 -3
- package/build/utils/markdown.ts +6 -17
- package/build/utils/paths.ts +3 -0
- package/build/utils/plain-text.ts +18 -0
- package/build/utils/render.ts +197 -154
- package/build/utils/shiki-highlighter.ts +11 -0
- package/build/utils/source-template.ts +2 -2
- package/build/utils/trace-core.ts +8 -83
- package/build/utils/trace.ts +38 -95
- package/build/validate-config-links.ts +39 -6
- package/build/watch/build-incremental.ts +46 -103
- package/build/watch/compiler.ts +158 -37
- package/build/watch/engine.ts +207 -162
- package/build/watch/index.ts +9 -2
- package/build/watch/planner.ts +88 -144
- package/build/watch/runtime.ts +21 -34
- package/build/watch/types.d.ts +32 -64
- package/package.json +4 -5
- package/schema/site.schema.json +18 -2
- package/src/_content.scss +447 -0
- package/src/_fonts.scss +90 -0
- package/src/_layout.scss +50 -35
- package/src/appearance-picker/{style.scss → _index.scss} +1 -1
- package/src/code/{style.scss → _index.scss} +61 -0
- package/src/header/{_base.scss → _index.scss} +98 -6
- package/src/index.ts +1 -17
- package/src/navigate/{style.scss → _index.scss} +20 -0
- package/src/navigate/eligible.ts +0 -40
- package/src/navigate/index.ts +50 -5
- package/src/navigate/lifecycle.ts +19 -2
- package/src/navigate/runtime.ts +20 -4
- package/src/page-update/index.ts +33 -20
- package/src/print/{style.scss → _index.scss} +3 -1
- package/src/search/index.ts +30 -2
- package/src/slides/{style.scss → _index.scss} +5 -0
- package/src/style.scss +24 -536
- package/src/timezone/index.ts +29 -3
- package/src/timezone/time-format.ts +4 -0
- package/src/toc/index.ts +6 -5
- package/src/trace/index.ts +11 -1
- package/templates/_author.html +2 -2
- package/templates/_download.html +1 -1
- package/templates/_footer.html +1 -1
- package/templates/_heading.html +3 -3
- package/templates/_nav.html +2 -2
- package/templates/_page-bottom.html +6 -0
- package/templates/_top.html +18 -14
- package/templates/code.html +22 -21
- package/templates/default.html +12 -11
- package/templates/literate.html +6 -5
- package/build/copy.ts +0 -82
- package/build/generate-content-assets.ts +0 -121
- package/build/utils/content-files.ts +0 -170
- package/build/watch/assets.ts +0 -103
- package/build/watch/build-full.ts +0 -97
- package/build/watch/build-helpers.ts +0 -66
- package/build/watch/build-result.ts +0 -19
- package/build/watch/fs-commit.ts +0 -153
- package/build/watch/mutations.ts +0 -73
- package/build/watch/snapshot.ts +0 -201
- package/build/watch/validation.ts +0 -50
- package/src/code.scss +0 -60
- package/src/critical.scss +0 -5
- package/src/header/style.scss +0 -91
- /package/src/{literate.scss → _literate.scss} +0 -0
- /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
- /package/src/anchor/{style.scss → _index.scss} +0 -0
- /package/src/page-update/{style.scss → _index.scss} +0 -0
- /package/src/question/{style.scss → _index.scss} +0 -0
- /package/src/search/{style.scss → _index.scss} +0 -0
- /package/src/timezone/{style.scss → _index.scss} +0 -0
- /package/src/toc/{style.scss → _index.scss} +0 -0
- /package/src/trace/{style.scss → _index.scss} +0 -0
package/build/bundle.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
import
|
|
3
|
+
import { compileTemplate } from './lodash-template';
|
|
5
4
|
import * as sass from 'sass';
|
|
6
|
-
import {
|
|
7
|
-
getPackageDir,
|
|
8
|
-
getProjectDir,
|
|
9
|
-
getDistDir,
|
|
10
|
-
toPosix,
|
|
11
|
-
} from './utils/paths';
|
|
5
|
+
import { getPackageDir, toPosix } from './utils/paths';
|
|
12
6
|
import {
|
|
13
7
|
deriveTheme,
|
|
14
8
|
deriveLinkHue,
|
|
@@ -40,7 +34,7 @@ interface CoverageGlobal {
|
|
|
40
34
|
__tadaCoverage?: CoverageHooks;
|
|
41
35
|
}
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
function getBundleNaming(): string {
|
|
44
38
|
const version = pkg.version.replace(/[^a-zA-Z0-9.-]/g, '-');
|
|
45
39
|
return `[name].bundle.tada-${version}.[ext]`;
|
|
46
40
|
}
|
|
@@ -53,6 +47,7 @@ function formatCssNumber(value: number, precision = 4): string {
|
|
|
53
47
|
return value.toFixed(precision).replace(/\.?0+$/, '');
|
|
54
48
|
}
|
|
55
49
|
|
|
50
|
+
// Returns the rendered SCSS source of the theme module (`config/theme`)
|
|
56
51
|
function renderThemeScss(siteVariables: SiteVariables): string {
|
|
57
52
|
const templatePath = path.join(getPackageDir(), 'templates/_theme.scss');
|
|
58
53
|
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
@@ -74,7 +69,7 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
74
69
|
siteVariables.fontOverrides,
|
|
75
70
|
);
|
|
76
71
|
|
|
77
|
-
const renderedTemplate =
|
|
72
|
+
const renderedTemplate = compileTemplate(template)({
|
|
78
73
|
...theme,
|
|
79
74
|
tintHue,
|
|
80
75
|
tintAmount,
|
|
@@ -98,12 +93,7 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
98
93
|
.replace('/* TADA_CUSTOM_FONT_TUNING */', customFontTuning)
|
|
99
94
|
.replace('/* TADA_MATERIAL_SYMBOL_VARIABLES */', materialSymbolVariables);
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
const configDir = path.join(tmpDir, 'config');
|
|
103
|
-
fs.mkdirSync(configDir);
|
|
104
|
-
fs.writeFileSync(path.join(configDir, '_theme.scss'), rendered);
|
|
105
|
-
|
|
106
|
-
return tmpDir;
|
|
96
|
+
return rendered;
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
function createDefine(
|
|
@@ -119,14 +109,31 @@ function createDefine(
|
|
|
119
109
|
};
|
|
120
110
|
}
|
|
121
111
|
|
|
122
|
-
|
|
112
|
+
const THEME_MODULE_URL = 'config/theme';
|
|
113
|
+
const THEME_CANONICAL_URL = 'tada:config/theme';
|
|
114
|
+
|
|
115
|
+
// Serves the rendered theme from memory to `@use 'config/theme'`
|
|
116
|
+
function createThemeImporter(themeScss: string): sass.Importer<'sync'> {
|
|
117
|
+
return {
|
|
118
|
+
canonicalize(url) {
|
|
119
|
+
return url === THEME_MODULE_URL ? new URL(THEME_CANONICAL_URL) : null;
|
|
120
|
+
},
|
|
121
|
+
load(canonicalUrl) {
|
|
122
|
+
if (canonicalUrl.href !== THEME_CANONICAL_URL) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return { contents: themeScss, syntax: 'scss' };
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createScssPlugin(themeScss: string) {
|
|
131
|
+
const importers = [createThemeImporter(themeScss)];
|
|
123
132
|
return {
|
|
124
133
|
name: 'scss',
|
|
125
134
|
setup(build: PluginBuilder) {
|
|
126
135
|
build.onLoad({ filter: /\.scss$/ }, args => {
|
|
127
|
-
const result = sass.compile(args.path, {
|
|
128
|
-
loadPaths: [themeDir, getProjectDir()],
|
|
129
|
-
});
|
|
136
|
+
const result = sass.compile(args.path, { importers });
|
|
130
137
|
return { contents: result.css, loader: 'css' as const };
|
|
131
138
|
});
|
|
132
139
|
},
|
|
@@ -139,55 +146,58 @@ function getCoverageBundlePlugin(): BunBuildPlugin | null {
|
|
|
139
146
|
);
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
|
|
149
|
+
async function buildToMemory(
|
|
150
|
+
options: Parameters<typeof Bun.build>[0],
|
|
151
|
+
): Promise<Map<string, string>> {
|
|
152
|
+
const result = await Bun.build(options);
|
|
153
|
+
if (!result.success) {
|
|
154
|
+
const messages = result.logs
|
|
155
|
+
.filter(log => log.level === 'error')
|
|
156
|
+
.map(log => log.message || String(log));
|
|
157
|
+
throw new Error(`Bundle failed:\n${messages.join('\n')}`);
|
|
158
|
+
}
|
|
159
|
+
const outputs = new Map<string, string>();
|
|
160
|
+
for (const output of result.outputs) {
|
|
161
|
+
outputs.set(
|
|
162
|
+
path.posix.normalize(toPosix(output.path)),
|
|
163
|
+
await output.text(),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return outputs;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Bundles Tada's client CSS and JavaScript, keyed by output path */
|
|
170
|
+
export function bundle(
|
|
143
171
|
siteVariables: SiteVariables,
|
|
144
|
-
{ mode = 'development'
|
|
145
|
-
): Promise<string
|
|
146
|
-
const packageDir = getPackageDir();
|
|
147
|
-
const resolvedDistDir = distDir ?? getDistDir();
|
|
172
|
+
{ mode = 'development' }: { mode?: string } = {},
|
|
173
|
+
): Promise<Map<string, string>> {
|
|
148
174
|
const isDev = mode === 'development';
|
|
175
|
+
const plugins = [createScssPlugin(renderThemeScss(siteVariables))];
|
|
176
|
+
const coverageBundlePlugin = getCoverageBundlePlugin();
|
|
177
|
+
if (coverageBundlePlugin) {
|
|
178
|
+
plugins.push(coverageBundlePlugin);
|
|
179
|
+
}
|
|
149
180
|
|
|
150
|
-
|
|
151
|
-
path.resolve(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (coverageBundlePlugin) {
|
|
161
|
-
plugins.push(coverageBundlePlugin);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const result = await Bun.build({
|
|
165
|
-
entrypoints,
|
|
166
|
-
outdir: resolvedDistDir,
|
|
167
|
-
naming: getBundleNaming(),
|
|
168
|
-
minify: mode === 'production',
|
|
169
|
-
sourcemap: isDev ? 'inline' : 'none',
|
|
170
|
-
define: createDefine(siteVariables, isDev),
|
|
171
|
-
external: ['*.woff2'],
|
|
172
|
-
plugins,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
if (!result.success) {
|
|
176
|
-
const messages = result.logs
|
|
177
|
-
.filter(log => log.level === 'error')
|
|
178
|
-
.map(log => log.message || String(log));
|
|
179
|
-
throw new Error(`Bundle failed:\n${messages.join('\n')}`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Return the output filenames for asset tag injection
|
|
183
|
-
const assetFiles = result.outputs.map(output =>
|
|
184
|
-
toPosix(path.relative(resolvedDistDir, output.path)),
|
|
185
|
-
);
|
|
181
|
+
return buildToMemory({
|
|
182
|
+
entrypoints: [path.resolve(getPackageDir(), 'src/index.ts')],
|
|
183
|
+
naming: getBundleNaming(),
|
|
184
|
+
minify: mode === 'production',
|
|
185
|
+
sourcemap: isDev ? 'inline' : 'none',
|
|
186
|
+
define: createDefine(siteVariables, isDev),
|
|
187
|
+
external: ['*.woff2'],
|
|
188
|
+
plugins,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
186
191
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
/** Bundles the watch-mode reload client, keyed by output path */
|
|
193
|
+
export function bundleReloadClient(): Promise<Map<string, string>> {
|
|
194
|
+
return buildToMemory({
|
|
195
|
+
entrypoints: [
|
|
196
|
+
path.resolve(getPackageDir(), 'build/watch-reload-client.ts'),
|
|
197
|
+
],
|
|
198
|
+
naming: getBundleNaming(),
|
|
199
|
+
sourcemap: 'inline',
|
|
200
|
+
});
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
export { renderThemeScss };
|
package/build/columns-plugin.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import type Token from 'markdown-it/lib/token.mjs';
|
|
2
3
|
import type StateBlock from 'markdown-it/lib/rules_block/state_block.mjs';
|
|
3
4
|
|
|
4
5
|
const MARKER_CHAR = 0x2b; // '+'
|
|
5
6
|
const MIN_MARKERS = 3;
|
|
6
7
|
|
|
8
|
+
// A separate block parser avoids invoking column rules recursively or mutating
|
|
9
|
+
// the document's tokens and plugin environment while locating literal code.
|
|
10
|
+
const codeContextParser = new MarkdownIt('commonmark');
|
|
11
|
+
|
|
7
12
|
function isColumnsFence(state: StateBlock, line: number): boolean {
|
|
8
13
|
const start = state.bMarks[line] + state.tShift[line];
|
|
9
14
|
const max = state.eMarks[line];
|
|
@@ -46,7 +51,28 @@ function columnsRule(
|
|
|
46
51
|
let separatorLine = -1;
|
|
47
52
|
let closeLine = -1;
|
|
48
53
|
|
|
54
|
+
// Parse block context, including lists and blockquotes, before looking for
|
|
55
|
+
// delimiters. Token maps keep code fences and their contents literal.
|
|
56
|
+
const contextTokens: Token[] = [];
|
|
57
|
+
codeContextParser.block.parse(
|
|
58
|
+
state.getLines(startLine + 1, endLine, state.blkIndent, false),
|
|
59
|
+
codeContextParser,
|
|
60
|
+
{},
|
|
61
|
+
contextTokens,
|
|
62
|
+
);
|
|
63
|
+
const codeLines = new Set<number>();
|
|
64
|
+
for (const token of contextTokens) {
|
|
65
|
+
if ((token.type === 'fence' || token.type === 'code_block') && token.map) {
|
|
66
|
+
for (let line = token.map[0]; line < token.map[1]; line++) {
|
|
67
|
+
codeLines.add(startLine + 1 + line);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
for (let line = startLine + 1; line < endLine; line++) {
|
|
73
|
+
if (codeLines.has(line)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
50
76
|
if (!isColumnsFence(state, line)) {
|
|
51
77
|
continue;
|
|
52
78
|
}
|
package/build/config-loader.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import
|
|
2
|
+
import { compileTemplate } from './lodash-template';
|
|
3
3
|
import {
|
|
4
4
|
resolveProjectConfigFile,
|
|
5
5
|
resolveSiteConfigFile,
|
|
@@ -44,7 +44,7 @@ function renderConfigTemplateString(
|
|
|
44
44
|
fileName: string,
|
|
45
45
|
): string {
|
|
46
46
|
try {
|
|
47
|
-
return
|
|
47
|
+
return compileTemplate(value)(context);
|
|
48
48
|
} catch (error) {
|
|
49
49
|
throw new Error(
|
|
50
50
|
`${fileName}: Lodash template error in config: ${getTemplateErrorMessage(error)}`,
|
|
@@ -61,7 +61,7 @@ function evaluateConfigTemplateExpression(
|
|
|
61
61
|
let captured: unknown;
|
|
62
62
|
|
|
63
63
|
try {
|
|
64
|
-
|
|
64
|
+
compileTemplate(`<% __capture(( ${expression} )); %>`)({
|
|
65
65
|
...context,
|
|
66
66
|
__capture(value: unknown) {
|
|
67
67
|
captured = value;
|
package/build/custom-fonts.ts
CHANGED
|
@@ -41,14 +41,17 @@ interface CustomFontValidationOptions {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export function isValidPublicWoff2Path(filePath: string): boolean {
|
|
44
|
+
return isValidPublicAssetPath(filePath) && filePath.endsWith('.woff2');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isValidPublicAssetPath(filePath: string): boolean {
|
|
44
48
|
if (
|
|
45
49
|
filePath.length === 0 ||
|
|
46
50
|
filePath.startsWith('/') ||
|
|
47
51
|
/^[A-Za-z]:/.test(filePath) ||
|
|
48
52
|
filePath.includes('\\') ||
|
|
49
53
|
filePath.includes('?') ||
|
|
50
|
-
filePath.includes('#')
|
|
51
|
-
!filePath.endsWith('.woff2')
|
|
54
|
+
filePath.includes('#')
|
|
52
55
|
) {
|
|
53
56
|
return false;
|
|
54
57
|
}
|
|
@@ -160,7 +163,7 @@ export function renderCustomFontTuningScss(
|
|
|
160
163
|
serifTuning?.lineHeight !== undefined
|
|
161
164
|
) {
|
|
162
165
|
rules.push(
|
|
163
|
-
` ${rootSelector} main.body > :is(
|
|
166
|
+
` ${rootSelector} main.body .page-bottom, ${rootSelector} main.body > .page-content > :is(.slides-header, .file-header) {\n font-size: var(--font-size);\n line-height: var(--line-height);\n }`,
|
|
164
167
|
);
|
|
165
168
|
}
|
|
166
169
|
|
|
@@ -206,7 +209,7 @@ export function renderCustomFontTuningScss(
|
|
|
206
209
|
export function getSerifFontStack(
|
|
207
210
|
fontOverrides: FontOverrides | undefined,
|
|
208
211
|
): string {
|
|
209
|
-
const custom = fontOverrides?.serif
|
|
212
|
+
const custom = fontOverrides?.serif?.regular
|
|
210
213
|
? `'${CUSTOM_FONT_FAMILY_DEFINITIONS.serif.cssFamily}', `
|
|
211
214
|
: '';
|
|
212
215
|
return `${custom}'Source Serif 4', 'Times New Roman', 'Times', serif`;
|
|
@@ -215,7 +218,7 @@ export function getSerifFontStack(
|
|
|
215
218
|
export function getSerifMonoFontStack(
|
|
216
219
|
fontOverrides: FontOverrides | undefined,
|
|
217
220
|
): string {
|
|
218
|
-
const custom = fontOverrides?.serifMono
|
|
221
|
+
const custom = fontOverrides?.serifMono?.regular
|
|
219
222
|
? `'${CUSTOM_FONT_FAMILY_DEFINITIONS.serifMono.cssFamily}', `
|
|
220
223
|
: '';
|
|
221
224
|
return `${custom}'Courier Prime', 'Courier New', 'Courier', monospace`;
|
|
@@ -24,11 +24,11 @@ export default function deflistIdPlugin(md: MarkdownIt): void {
|
|
|
24
24
|
break;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
if (t.type === 'text') {
|
|
27
|
+
if (t.type === 'text' || t.type === 'code_inline') {
|
|
28
28
|
termText += t.content;
|
|
29
29
|
} else if (t.type === 'inline' && t.children) {
|
|
30
30
|
for (const child of t.children) {
|
|
31
|
-
if (child.type === 'text') {
|
|
31
|
+
if (child.type === 'text' || child.type === 'code_inline') {
|
|
32
32
|
termText += child.content;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import * as fontkit from 'fontkit';
|
|
4
3
|
import sharp from 'sharp';
|
|
@@ -108,10 +107,11 @@ function createFaviconSvg(
|
|
|
108
107
|
</svg>`;
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
/** Generated favicon files, keyed by output path */
|
|
111
111
|
export async function generateFavicons(
|
|
112
112
|
siteVariables: SiteVariables,
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
): Promise<Map<string, string | Uint8Array>> {
|
|
114
|
+
const outputs = new Map<string, string | Uint8Array>();
|
|
115
115
|
const color = siteVariables.faviconColor!;
|
|
116
116
|
const symbol = siteVariables.faviconSymbol!;
|
|
117
117
|
const fontWeight = siteVariables.faviconFontWeight || 700;
|
|
@@ -132,7 +132,7 @@ export async function generateFavicons(
|
|
|
132
132
|
font,
|
|
133
133
|
fontWeight,
|
|
134
134
|
);
|
|
135
|
-
|
|
135
|
+
outputs.set(`${filenameBase}.svg`, svgMarkup);
|
|
136
136
|
|
|
137
137
|
const pngBuffers = await Promise.all(
|
|
138
138
|
FAVICON_SIZES.map(async size => {
|
|
@@ -145,7 +145,7 @@ export async function generateFavicons(
|
|
|
145
145
|
fontWeight,
|
|
146
146
|
);
|
|
147
147
|
const buf = await sharp(Buffer.from(svgForSize)).png().toBuffer();
|
|
148
|
-
|
|
148
|
+
outputs.set(`${filenameBase}-${size}.png`, buf);
|
|
149
149
|
return { size, buf };
|
|
150
150
|
}),
|
|
151
151
|
);
|
|
@@ -156,5 +156,6 @@ export async function generateFavicons(
|
|
|
156
156
|
.sort((a, b) => a.size - b.size)
|
|
157
157
|
.map(x => x.buf),
|
|
158
158
|
);
|
|
159
|
-
|
|
159
|
+
outputs.set(`${filenameBase}.ico`, icoBuffer);
|
|
160
|
+
return outputs;
|
|
160
161
|
}
|
package/build/generate-fonts.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getPackageDir } from './utils/paths';
|
|
4
|
-
import {
|
|
5
|
-
import type { SiteVariables } from './types';
|
|
4
|
+
import type { CopiedFile, SiteVariables } from './types';
|
|
6
5
|
|
|
7
|
-
const log = makeLogger(import.meta.url);
|
|
8
6
|
const FONTS_DIR = path.join(getPackageDir(), 'fonts');
|
|
9
7
|
|
|
10
8
|
export const DEFAULT_FONT_PRELOAD_FILES = {
|
|
@@ -49,26 +47,21 @@ export function getDefaultFontPreloadFiles(
|
|
|
49
47
|
];
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
/** Bundled browser fonts, as copy references keyed by output path */
|
|
51
|
+
export function getFontOutputs(): Map<string, CopiedFile> {
|
|
52
|
+
const outputs = new Map<string, CopiedFile>();
|
|
55
53
|
for (const family of fs.readdirSync(FONTS_DIR)) {
|
|
56
54
|
const woff2Dir = path.join(FONTS_DIR, family, 'woff2');
|
|
57
55
|
if (!fs.existsSync(woff2Dir) || !fs.statSync(woff2Dir).isDirectory()) {
|
|
58
56
|
continue;
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
const outFamilyDir = path.join(distDir, family);
|
|
62
|
-
fs.mkdirSync(outFamilyDir, { recursive: true });
|
|
63
|
-
|
|
64
58
|
for (const file of fs.readdirSync(woff2Dir)) {
|
|
65
59
|
if (file.endsWith('.woff2')) {
|
|
66
|
-
|
|
67
|
-
path.join(woff2Dir, file),
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
log.debug`Copied ${family}/${file}`;
|
|
60
|
+
outputs.set(`${family}/${file}`, {
|
|
61
|
+
copyFrom: path.join(woff2Dir, file),
|
|
62
|
+
});
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
}
|
|
66
|
+
return outputs;
|
|
74
67
|
}
|
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import * as sass from 'sass';
|
|
4
|
-
import {
|
|
4
|
+
import type { OutputContent } from './types';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function copyKatexAssets(distDir: string): void {
|
|
9
|
-
log.info`Copying KaTeX assets`;
|
|
10
|
-
|
|
11
|
-
const outDir = path.join(distDir, 'katex');
|
|
12
|
-
const outFontsDir = path.join(outDir, 'fonts');
|
|
13
|
-
fs.mkdirSync(outFontsDir, { recursive: true });
|
|
6
|
+
let katexCss: string | undefined;
|
|
14
7
|
|
|
8
|
+
function compileKatexCss(): string {
|
|
15
9
|
// Compile KaTeX SCSS with woff2-only font references
|
|
16
10
|
const katexScssDir = path.dirname(
|
|
17
11
|
require.resolve('katex/src/styles/katex.scss'),
|
|
18
12
|
);
|
|
19
|
-
|
|
13
|
+
return sass.compileString(
|
|
20
14
|
`@use 'katex' with ($use-woff2: true, $use-woff: false, $use-ttf: false, $font-folder: 'fonts');`,
|
|
21
15
|
{ loadPaths: [katexScssDir], style: 'compressed' },
|
|
22
|
-
);
|
|
23
|
-
|
|
16
|
+
).css;
|
|
17
|
+
}
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
/** The KaTeX stylesheet and its woff2 fonts, keyed by output path */
|
|
20
|
+
export function getKatexOutputs(): Map<string, OutputContent> {
|
|
21
|
+
katexCss ??= compileKatexCss();
|
|
22
|
+
const outputs = new Map<string, OutputContent>([
|
|
23
|
+
['katex/katex.min.css', katexCss],
|
|
24
|
+
]);
|
|
25
|
+
const fontsDir = path.join(
|
|
26
|
+
path.dirname(require.resolve('katex/dist/katex.min.css')),
|
|
27
|
+
'fonts',
|
|
28
28
|
);
|
|
29
|
-
const fontsDir = path.join(katexDistDir, 'fonts');
|
|
30
29
|
for (const file of fs.readdirSync(fontsDir)) {
|
|
31
30
|
if (file.endsWith('.woff2')) {
|
|
32
|
-
|
|
31
|
+
outputs.set(`katex/fonts/${file}`, {
|
|
32
|
+
copyFrom: path.join(fontsDir, file),
|
|
33
|
+
});
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
return outputs;
|
|
35
37
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { createApplyBasePath } from './util';
|
|
4
2
|
import { FAVICON_SIZES } from './generate-favicon';
|
|
5
3
|
import type { SiteVariables } from './types';
|
|
@@ -57,13 +55,7 @@ function createManifest(siteVariables: SiteVariables): WebAppManifest {
|
|
|
57
55
|
};
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
): void {
|
|
64
|
-
const manifest = createManifest(siteVariables);
|
|
65
|
-
fs.writeFileSync(
|
|
66
|
-
path.join(distDir, 'manifest.json'),
|
|
67
|
-
JSON.stringify(manifest),
|
|
68
|
-
);
|
|
58
|
+
/** The web app manifest (`manifest.json`) for generated favicons */
|
|
59
|
+
export function createWebAppManifest(siteVariables: SiteVariables): string {
|
|
60
|
+
return JSON.stringify(createManifest(siteVariables));
|
|
69
61
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compiles a Lodash template that only recognizes `<% %>`, `<%= %>`, and
|
|
5
|
+
* `<%- %>` delimiters.
|
|
6
|
+
*
|
|
7
|
+
* Lodash also treats ES template literal syntax (`${expr}`) as interpolation,
|
|
8
|
+
* but only while `interpolate` is its built-in regex object. Passing a separate
|
|
9
|
+
* regex literal with the same pattern disables that, so `${...}` in Markdown,
|
|
10
|
+
* code samples, and source files stays literal text.
|
|
11
|
+
*/
|
|
12
|
+
export function compileTemplate(source: string): _.TemplateExecutor {
|
|
13
|
+
return _.template(source, { interpolate: /<%=([\s\S]+?)%>/g });
|
|
14
|
+
}
|