Repository navigation
Expand file tree
/
Copy pathvite.config.ts
More file actions
119 lines (115 loc) · 3.56 KB
/
Copy pathvite.config.ts
File metadata and controls
119 lines (115 loc) · 3.56 KB
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/// <reference types="vitest" />
import {defineConfig, type Plugin} from 'vite';
import analog from '@analogjs/platform';
import tailwindcss from '@tailwindcss/vite';
import {readFileSync} from 'node:fs';
import {getBuildExtensions} from './src/marked-extensions/index.ts';
import {pageMetaPlugin} from './page-meta.plugin.ts';
import {internalLinkGuard} from './link-guard.plugin.ts';
import {mdLinksPlugin} from './md-links.plugin.ts';
import {sitemapPlugin} from './sitemap.plugin.ts';
import {searchIndexPlugin} from './search-index.plugin.ts';
import {rawMdPlugin} from './raw-md.plugin.ts';
import {varsPlugin} from './vars.plugin.ts';
import {apiGenPlugin} from './api-gen.plugin.ts';
import {withoutCode} from './plugin-utils.ts';
import config from './src/ngmd.config.ts';
/**
* Build-time guard: errors when a markdown file in `src/content/` contains
* a raw HTML `<a href="http(s)://...">` without `target="_blank"`. Raw HTML
* anchors bypass the marked link renderer (which would add target=_blank
* automatically), so this catches external links that would silently open
* in the same tab.
*
* Lifted from the adev docs pipeline pattern.
*/
function externalLinkGuard(): Plugin {
return {
name: 'ngmd-external-link-guard',
enforce: 'pre',
transform(_code, id) {
const file = id.split('?')[0];
if (!file.endsWith('.md')) return null;
const content = withoutCode(readFileSync(file, 'utf8'));
const anchorRe = /<a\b[^>]*href=["']https?:\/\/[^"']+["'][^>]*>/g;
const matches = content.match(anchorRe) ?? [];
for (const m of matches) {
if (!/target=["']_blank["']/.test(m)) {
this.error(
`[ngmd] External anchor in ${file} is missing target="_blank":\n ${m}\n` +
`Add target="_blank" rel="noopener noreferrer" so external links open in a new tab.`,
);
}
}
return null;
},
};
}
function siteHtml(): Plugin {
const escape = (value: string) =>
value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
const values: Record<string, string> = {
'%SITE_NAME%': config.site.name,
'%SITE_DESCRIPTION%': config.site.description,
'%SITE_URL%': config.site.url.replace(/\/+$/, ''),
};
return {
name: 'site-html',
transformIndexHtml(html) {
return Object.entries(values).reduce(
(out, [token, value]) => out.replaceAll(token, escape(value)),
html,
);
},
};
}
export default defineConfig(async () => ({
build: {
target: ['es2020'],
},
resolve: {
mainFields: ['module'],
},
plugins: [
siteHtml(),
varsPlugin(),
externalLinkGuard(),
internalLinkGuard(),
mdLinksPlugin(),
pageMetaPlugin({
repoUrl: config.site.githubUrl,
branch: config.site.githubBranch ?? 'main',
dir: config.site.githubDir,
}),
sitemapPlugin({siteUrl: config.site.url}),
rawMdPlugin(),
searchIndexPlugin(),
apiGenPlugin(),
analog({
apiPrefix: '_server',
content: {
highlighter: 'shiki',
markedOptions: {
extensions: await getBuildExtensions(),
},
shikiOptions: {
highlight: {
themes: {light: 'github-light-default', dark: 'github-dark-default'},
defaultColor: false,
},
highlighter: {
additionalLangs: ['bash', 'md', 'json'],
},
},
},
}),
tailwindcss(),
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
include: ['**/*.spec.ts'],
reporters: ['default'],
},
}));