mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
* feat(sci): add 4chan-style TeX support with MathJax on /sci/ - [math]/[eqn] tags typeset with MathJax 3 (lazy chunk, only on /sci/ with math present) - 4chan-identical config: Safe mode, left-aligned eqn, neutered \color/\newcommand macros - TeX button in reply modal title bar opens live TeX Preview modal - /sci/ post form rules bullets for [math]/[eqn] usage and right-click source - MathJax context menu on right-click (Show Math As > TeX Commands) - woff fonts served from node_modules in dev and emitted into build * feat(sci): finish TeX support: configmacros fix, preview preload, translations, tests - add configmacros package so the 4chan macro neutering (\color, \newcommand, ...) applies - preload MathJax when the TeX Preview opens, like 4chan - stable closeModal callback for the preview modal - pre-bundle mathjax components in vite optimizeDeps to avoid dev mid-session reload - translate the 6 new TeX keys into all 35 languages - markdown math segment component tests + math-tags unit tests * feat(reply modal): add sci tex preview button * fix(tex-preview): clear MathJax bookkeeping and pending typeset on close Addresses Cursor Bugbot: the preview output was typeset via typesetMathElement but never passed to clearMathElement on unmount, so repeated open/close cycles kept detached nodes in MathJax's math list. Also cancels the pending debounce timer. * fix(reply-modal): reset TeX preview state when the reply modal closes Addresses CodeRabbit: showTexPreview persisted across close/reopen like the bbcode preview flags, so the TeX preview would auto-open on the next reply.
590 lines
18 KiB
JavaScript
590 lines
18 KiB
JavaScript
import { defineConfig } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import { resolve } from 'path';
|
|
import { readdirSync, readFileSync } from 'fs';
|
|
import { createHash } from 'crypto';
|
|
import { execFileSync } from 'child_process';
|
|
import { VitePWA } from 'vite-plugin-pwa';
|
|
|
|
const { version: packageVersion } = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
const appVersion = `${process.env.VITE_APP_VERSION || packageVersion}`.trim() || packageVersion;
|
|
process.env.VITE_APP_VERSION = appVersion;
|
|
const releaseTag = `v${appVersion.replace(/^v/i, '').split('-')[0]}`;
|
|
const publicBase = process.env.PUBLIC_URL || '/';
|
|
const buildOutDir = 'build';
|
|
const basePathPrefix = (() => {
|
|
const pathname = new URL(publicBase, 'https://example.invalid/').pathname;
|
|
return pathname === '/' ? '' : pathname.replace(/^\/+|\/+$/g, '');
|
|
})();
|
|
const devServerHmr = (() => {
|
|
if (!process.env.PORTLESS_URL) {
|
|
return { overlay: false };
|
|
}
|
|
|
|
try {
|
|
const url = new URL(process.env.PORTLESS_URL);
|
|
const isHttps = url.protocol === 'https:';
|
|
|
|
return {
|
|
overlay: false,
|
|
protocol: isHttps ? 'wss' : 'ws',
|
|
host: url.hostname,
|
|
clientPort: Number(url.port || (isHttps ? 443 : 80)),
|
|
};
|
|
} catch {
|
|
return { overlay: false };
|
|
}
|
|
})();
|
|
const neverPrecacheUrls = new Set(['index.html', 'version.json']);
|
|
const vitePwaManagedAssetUrls = new Set([
|
|
'manifest.webmanifest',
|
|
'favicon.ico',
|
|
'favicon2.ico',
|
|
'robots.txt',
|
|
'apple-touch-icon.png',
|
|
'manifest-icon-192x192.png',
|
|
'manifest-icon-512x512.png',
|
|
]);
|
|
const baselineAppShellUrls = new Set([
|
|
'registerSW.js',
|
|
'manifest.json',
|
|
'manifest.webmanifest',
|
|
'favicon.ico',
|
|
'favicon2.ico',
|
|
'robots.txt',
|
|
'apple-touch-icon.png',
|
|
'manifest-icon-192x192.png',
|
|
'manifest-icon-512x512.png',
|
|
]);
|
|
const ruffleRuntimeDirectory = new URL('./node_modules/@ruffle-rs/ruffle/', import.meta.url);
|
|
const ruffleRuntimeFilePattern = /^(?:ruffle\.js|core\.ruffle\.[\da-f]+\.js|[\da-f]+\.wasm)$/;
|
|
const mathjaxFontDirectory = new URL('./node_modules/mathjax-full/es5/output/chtml/fonts/woff-v2/', import.meta.url);
|
|
const mathjaxFontFilePattern = /^MathJax_[\w-]+\.woff$/;
|
|
|
|
function readGitRef(args) {
|
|
try {
|
|
return execFileSync('git', args, {
|
|
cwd: new URL('.', import.meta.url),
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
timeout: 5000,
|
|
}).trim();
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
return values.map((value) => `${value || ''}`.trim()).find(Boolean) || '';
|
|
}
|
|
|
|
function normalizeCommitRef(ref) {
|
|
return `${ref || ''}`.trim().toLowerCase();
|
|
}
|
|
|
|
function isSameCommitRef(left, right) {
|
|
const normalizedLeft = normalizeCommitRef(left);
|
|
const normalizedRight = normalizeCommitRef(right);
|
|
|
|
if (!normalizedLeft || !normalizedRight) {
|
|
return false;
|
|
}
|
|
|
|
return normalizedLeft === normalizedRight || normalizedLeft.startsWith(normalizedRight) || normalizedRight.startsWith(normalizedLeft);
|
|
}
|
|
|
|
function readRemoteTagCommitRef(tagName) {
|
|
const tagRef = `refs/tags/${tagName}`;
|
|
const output = readGitRef(['ls-remote', '--tags', 'origin', tagRef, `${tagRef}^{}`]);
|
|
const lines = output
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter(Boolean);
|
|
const releaseLine = lines.find((line) => line.endsWith(`${tagRef}^{}`)) || lines.find((line) => line.endsWith(tagRef)) || '';
|
|
|
|
return releaseLine.split(/\s+/)[0] || '';
|
|
}
|
|
|
|
function resolveBuildCommitRef() {
|
|
return firstNonEmpty(process.env.VITE_COMMIT_REF, process.env.VERCEL_GIT_COMMIT_SHA, process.env.GITHUB_SHA, process.env.COMMIT_REF, readGitRef(['rev-parse', 'HEAD']));
|
|
}
|
|
|
|
function resolveReleaseCommitRef() {
|
|
const configuredReleaseCommitRef = firstNonEmpty(process.env.VITE_LATEST_RELEASE_COMMIT_REF, process.env.LATEST_RELEASE_COMMIT_REF);
|
|
|
|
if (configuredReleaseCommitRef) {
|
|
return configuredReleaseCommitRef;
|
|
}
|
|
|
|
if (process.env.GITHUB_REF_NAME === releaseTag) {
|
|
return resolveBuildCommitRef();
|
|
}
|
|
|
|
const localReleaseCommitRef = readGitRef(['rev-list', '-n', '1', releaseTag]);
|
|
|
|
return localReleaseCommitRef || readRemoteTagCommitRef(releaseTag);
|
|
}
|
|
|
|
const buildCommitRef = resolveBuildCommitRef();
|
|
const releaseCommitRef = resolveReleaseCommitRef();
|
|
const displayCommitRef = buildCommitRef && !isSameCommitRef(buildCommitRef, releaseCommitRef) ? buildCommitRef : '';
|
|
|
|
function normalizePrecacheUrl(url) {
|
|
const normalizedUrl = url.split('?')[0].replace(/^[./]+/, '');
|
|
|
|
if (basePathPrefix && normalizedUrl.startsWith(`${basePathPrefix}/`)) {
|
|
return normalizedUrl.slice(basePathPrefix.length + 1);
|
|
}
|
|
|
|
return normalizedUrl;
|
|
}
|
|
|
|
function collectIndexAssetUrls() {
|
|
const indexHtml = readFileSync(new URL(`./${buildOutDir}/index.html`, import.meta.url), 'utf8');
|
|
const urls = new Set(baselineAppShellUrls);
|
|
const assetAttributePattern = /\b(?:href|src)=["'](?:\.\/|\/)?([^"']+\.(?:css|ico|js|json|png|webmanifest))(?:\?[^"']*)?["']/g;
|
|
|
|
for (const match of indexHtml.matchAll(assetAttributePattern)) {
|
|
urls.add(normalizePrecacheUrl(match[1]));
|
|
}
|
|
|
|
return urls;
|
|
}
|
|
|
|
function keepAppShellPrecacheOnly(manifestEntries) {
|
|
const appShellUrls = collectIndexAssetUrls();
|
|
const manifest = manifestEntries.filter((entry) => {
|
|
const normalizedUrl = normalizePrecacheUrl(entry.url);
|
|
|
|
if (neverPrecacheUrls.has(normalizedUrl)) {
|
|
return false;
|
|
}
|
|
|
|
if (vitePwaManagedAssetUrls.has(normalizedUrl)) {
|
|
return false;
|
|
}
|
|
|
|
return appShellUrls.has(normalizedUrl);
|
|
});
|
|
|
|
return { manifest };
|
|
}
|
|
|
|
function appVersionMetadataPlugin() {
|
|
const payload = `${JSON.stringify({
|
|
version: appVersion,
|
|
commitRef: buildCommitRef || undefined,
|
|
releaseCommitRef: releaseCommitRef || undefined,
|
|
})}\n`;
|
|
|
|
return {
|
|
name: 'fivechan-version-metadata',
|
|
configureServer(server) {
|
|
server.middlewares.use('/version.json', (_req, res) => {
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
res.end(payload);
|
|
});
|
|
},
|
|
generateBundle() {
|
|
this.emitFile({
|
|
type: 'asset',
|
|
fileName: 'version.json',
|
|
source: payload,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function getRuffleRuntimeFileNames() {
|
|
return readdirSync(ruffleRuntimeDirectory).filter((fileName) => ruffleRuntimeFilePattern.test(fileName));
|
|
}
|
|
|
|
function getRuffleRuntimeContentType(fileName) {
|
|
return fileName.endsWith('.wasm') ? 'application/wasm' : 'application/javascript; charset=utf-8';
|
|
}
|
|
|
|
function ruffleRuntimeAssetsPlugin() {
|
|
return {
|
|
name: 'fivechan-ruffle-runtime-assets',
|
|
configureServer(server) {
|
|
server.middlewares.use('/ruffle/', (req, res, next) => {
|
|
const fileName = new URL(req.url || '', 'https://example.invalid/').pathname.split('/').pop() || '';
|
|
|
|
if (!ruffleRuntimeFilePattern.test(fileName)) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const source = readFileSync(new URL(fileName, ruffleRuntimeDirectory));
|
|
res.setHeader('Content-Type', getRuffleRuntimeContentType(fileName));
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.end(source);
|
|
} catch {
|
|
next();
|
|
}
|
|
});
|
|
},
|
|
generateBundle() {
|
|
for (const fileName of getRuffleRuntimeFileNames()) {
|
|
this.emitFile({
|
|
type: 'asset',
|
|
fileName: `ruffle/${fileName}`,
|
|
source: readFileSync(new URL(fileName, ruffleRuntimeDirectory)),
|
|
});
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
// MathJax's CHTML output loads its TeX web fonts from <base>/mathjax/woff-v2/ (see
|
|
// src/lib/mathjax/mathjax-config.ts). Like the Ruffle runtime, the fonts are served straight
|
|
// from node_modules in dev and emitted into the build output, so nothing is committed to git.
|
|
function getMathjaxFontFileNames() {
|
|
return readdirSync(mathjaxFontDirectory).filter((fileName) => mathjaxFontFilePattern.test(fileName));
|
|
}
|
|
|
|
function mathjaxFontAssetsPlugin() {
|
|
return {
|
|
name: 'fivechan-mathjax-font-assets',
|
|
configureServer(server) {
|
|
server.middlewares.use('/mathjax/woff-v2/', (req, res, next) => {
|
|
const fileName = new URL(req.url || '', 'https://example.invalid/').pathname.split('/').pop() || '';
|
|
|
|
if (!mathjaxFontFilePattern.test(fileName)) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const source = readFileSync(new URL(fileName, mathjaxFontDirectory));
|
|
res.setHeader('Content-Type', 'font/woff');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.end(source);
|
|
} catch {
|
|
next();
|
|
}
|
|
});
|
|
},
|
|
generateBundle() {
|
|
for (const fileName of getMathjaxFontFileNames()) {
|
|
this.emitFile({
|
|
type: 'asset',
|
|
fileName: `mathjax/woff-v2/${fileName}`,
|
|
source: readFileSync(new URL(fileName, mathjaxFontDirectory)),
|
|
});
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function getVercelContentSecurityPolicy() {
|
|
const vercelConfig = JSON.parse(readFileSync(new URL('./vercel.json', import.meta.url), 'utf8'));
|
|
const cspHeader = vercelConfig.headers
|
|
?.flatMap((entry) => entry.headers || [])
|
|
.find((header) => typeof header.key === 'string' && header.key.toLowerCase() === 'content-security-policy');
|
|
|
|
if (typeof cspHeader?.value !== 'string') {
|
|
throw new Error('vercel.json is missing a Content-Security-Policy header.');
|
|
}
|
|
|
|
return cspHeader.value;
|
|
}
|
|
|
|
function getInlineScriptHashes(indexHtml) {
|
|
const scriptPattern = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
const hashes = [];
|
|
|
|
for (const [, attributes, source] of indexHtml.matchAll(scriptPattern)) {
|
|
if (/\bsrc\s*=/i.test(attributes) || source.trim().length === 0) {
|
|
continue;
|
|
}
|
|
|
|
hashes.push(`sha256-${createHash('sha256').update(source).digest('base64')}`);
|
|
}
|
|
|
|
return [...new Set(hashes)];
|
|
}
|
|
|
|
function verifyVercelCspHashesPlugin() {
|
|
return {
|
|
name: 'fivechan-verify-vercel-csp-hashes',
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
closeBundle() {
|
|
const indexHtml = readFileSync(new URL(`./${buildOutDir}/index.html`, import.meta.url), 'utf8');
|
|
const contentSecurityPolicy = getVercelContentSecurityPolicy();
|
|
const missingHashes = getInlineScriptHashes(indexHtml).filter((hash) => !contentSecurityPolicy.includes(`'${hash}'`) && !contentSecurityPolicy.includes(hash));
|
|
|
|
if (missingHashes.length > 0) {
|
|
const plural = missingHashes.length === 1 ? '' : 'es';
|
|
|
|
throw new Error(`vercel.json Content-Security-Policy is missing inline script hash${plural}: ${missingHashes.join(', ')}`);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function adaptReactPluginForRolldown(plugin) {
|
|
if (!plugin?.config || plugin.name !== 'vite:react-babel') {
|
|
return plugin;
|
|
}
|
|
|
|
return {
|
|
...plugin,
|
|
async config(userConfig, configEnv) {
|
|
const config = await plugin.config.call(this, userConfig, configEnv);
|
|
const optimizeDeps = config?.optimizeDeps;
|
|
|
|
if (optimizeDeps?.esbuildOptions?.jsx !== 'automatic') {
|
|
return config;
|
|
}
|
|
|
|
const { esbuildOptions, ...remainingOptimizeDeps } = optimizeDeps;
|
|
|
|
return {
|
|
...config,
|
|
optimizeDeps: {
|
|
...remainingOptimizeDeps,
|
|
rolldownOptions: {
|
|
...optimizeDeps.rolldownOptions,
|
|
transform: {
|
|
...optimizeDeps.rolldownOptions?.transform,
|
|
jsx: optimizeDeps.rolldownOptions?.transform?.jsx ?? {
|
|
runtime: 'automatic',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
appVersionMetadataPlugin(),
|
|
ruffleRuntimeAssetsPlugin(),
|
|
mathjaxFontAssetsPlugin(),
|
|
...react({
|
|
babel: {
|
|
plugins: [
|
|
[
|
|
'babel-plugin-react-compiler',
|
|
{
|
|
verbose: true,
|
|
},
|
|
],
|
|
],
|
|
},
|
|
}).map(adaptReactPluginForRolldown),
|
|
VitePWA({
|
|
registerType: 'autoUpdate',
|
|
strategies: 'injectManifest',
|
|
injectManifest: {
|
|
maximumFileSizeToCacheInBytes: 20000000,
|
|
globPatterns: ['**/*.{css,html,ico,js,json,png,webmanifest}'],
|
|
manifestTransforms: [keepAppShellPrecacheOnly],
|
|
},
|
|
srcDir: 'src',
|
|
filename: 'sw.ts',
|
|
devOptions: {
|
|
enabled: true,
|
|
type: 'module',
|
|
},
|
|
includeAssets: ['favicon.ico', 'favicon2.ico', 'robots.txt', 'apple-touch-icon.png'],
|
|
manifest: {
|
|
name: '5chan',
|
|
short_name: '5chan',
|
|
description: 'A serverless, adminless, decentralized imageboard',
|
|
theme_color: '#ffffff',
|
|
background_color: '#ffffee',
|
|
display: 'standalone',
|
|
icons: [
|
|
{
|
|
src: 'manifest-icon-192x192.png',
|
|
sizes: '192x192',
|
|
type: 'image/png',
|
|
},
|
|
{
|
|
src: 'manifest-icon-512x512.png',
|
|
sizes: '512x512',
|
|
type: 'image/png',
|
|
},
|
|
{
|
|
src: 'manifest-icon-512x512.png',
|
|
sizes: '512x512',
|
|
type: 'image/png',
|
|
purpose: 'any maskable',
|
|
},
|
|
],
|
|
},
|
|
workbox: {
|
|
clientsClaim: true,
|
|
skipWaiting: true,
|
|
cleanupOutdatedCaches: true,
|
|
navigateFallback: 'index.html',
|
|
navigateFallbackDenylist: [/^\/api/, /^\/_\(.*\)/],
|
|
maximumFileSizeToCacheInBytes: 6000000,
|
|
runtimeCaching: [
|
|
// PNG caching
|
|
{
|
|
urlPattern: ({ url }) => url.pathname.endsWith('.png'),
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'images',
|
|
expiration: {
|
|
maxEntries: 50,
|
|
},
|
|
},
|
|
},
|
|
// Add additional asset caching
|
|
{
|
|
urlPattern: /\.(?:js|css|woff2?|svg|gif|jpg|jpeg)$/,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'assets-cache',
|
|
expiration: {
|
|
maxEntries: 100,
|
|
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
|
|
},
|
|
},
|
|
},
|
|
// Google Fonts caching
|
|
{
|
|
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
|
handler: 'CacheFirst',
|
|
options: {
|
|
cacheName: 'google-fonts-cache',
|
|
expiration: {
|
|
maxEntries: 10,
|
|
maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days
|
|
},
|
|
cacheableResponse: {
|
|
statuses: [0, 200],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
|
handler: 'CacheFirst',
|
|
options: {
|
|
cacheName: 'google-fonts-webfonts',
|
|
expiration: {
|
|
maxEntries: 30,
|
|
maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days
|
|
},
|
|
cacheableResponse: {
|
|
statuses: [0, 200],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
verifyVercelCspHashesPlugin(),
|
|
],
|
|
resolve: {
|
|
alias: {
|
|
'@': resolve(__dirname, 'src'),
|
|
'node-fetch': 'isomorphic-fetch',
|
|
assert: 'assert',
|
|
stream: 'stream-browserify',
|
|
crypto: 'crypto-browserify',
|
|
buffer: 'buffer',
|
|
events: 'events',
|
|
process: 'process',
|
|
'node:buffer': 'buffer',
|
|
'node:crypto': 'crypto-browserify',
|
|
'node:events': 'events',
|
|
'node:process': 'process',
|
|
'node:stream': 'stream-browserify',
|
|
'node:util': 'util/',
|
|
'util/': 'util/',
|
|
util: 'util/',
|
|
},
|
|
},
|
|
server: {
|
|
port: 3000,
|
|
open: process.env.PORTLESS_URL ? false : true,
|
|
watch: {
|
|
usePolling: true,
|
|
},
|
|
hmr: devServerHmr,
|
|
},
|
|
build: {
|
|
// Use 'build' to match what electron/main.js expects (../build/index.html)
|
|
outDir: buildOutDir,
|
|
emptyOutDir: true,
|
|
sourcemap: process.env.GENERATE_SOURCEMAP === 'true',
|
|
target: process.env.ELECTRON ? 'electron-renderer' : 'esnext',
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks(id) {
|
|
if (/[\\/]node_modules[\\/](@pkcprotocol[\\/]pkc-js)[\\/]/.test(id)) {
|
|
return 'pkc-js';
|
|
}
|
|
if (/[\\/]node_modules[\\/](@bitsocialnet[\\/]bitsocial-react-hooks)[\\/]/.test(id)) {
|
|
return 'bitsocial-react-hooks';
|
|
}
|
|
if (/[\\/]node_modules[\\/](@react-spring|@use-gesture)[\\/]/.test(id)) {
|
|
return 'spring-gesture';
|
|
}
|
|
if (/[\\/]node_modules[\\/](react|react-dom|react-router-dom|react-i18next|i18next|i18next-browser-languagedetector|i18next-http-backend)[\\/]/.test(id)) {
|
|
return 'vendor';
|
|
}
|
|
if (/[\\/]node_modules[\\/](react-markdown|remark-|rehype-|unified|micromark|mdast|hast|unist)[\\/]/.test(id)) {
|
|
return 'markdown';
|
|
}
|
|
if (/[\\/]node_modules[\\/](react-virtuoso)[\\/]/.test(id)) {
|
|
return 'virtuoso';
|
|
}
|
|
if (/[\\/]node_modules[\\/](mathjax-full|mj-context-menu)[\\/]/.test(id)) {
|
|
return 'mathjax';
|
|
}
|
|
if (/[\\/]node_modules[\\/](@floating-ui)[\\/]/.test(id)) {
|
|
return 'floating-ui';
|
|
}
|
|
},
|
|
},
|
|
},
|
|
},
|
|
base: publicBase,
|
|
optimizeDeps: {
|
|
include: [
|
|
'ethers',
|
|
'assert',
|
|
'buffer',
|
|
'process',
|
|
'util',
|
|
'stream-browserify',
|
|
'isomorphic-fetch',
|
|
'workbox-core',
|
|
'workbox-precaching',
|
|
// The lazy MathJax chunk (src/lib/mathjax/mathjax-setup.ts): pre-bundle so the first
|
|
// equation does not trigger a mid-session "optimized dependencies changed" full reload.
|
|
'mathjax-full/components/src/startup/lib/startup.js',
|
|
'mathjax-full/components/src/core/core.js',
|
|
'mathjax-full/components/src/input/tex-base/tex-base.js',
|
|
'mathjax-full/components/src/input/tex/extensions/ams/ams.js',
|
|
'mathjax-full/components/src/input/tex/extensions/configmacros/configmacros.js',
|
|
'mathjax-full/components/src/input/tex/extensions/noerrors/noerrors.js',
|
|
'mathjax-full/components/src/input/tex/extensions/noundefined/noundefined.js',
|
|
'mathjax-full/components/src/output/chtml/chtml.js',
|
|
'mathjax-full/components/src/output/chtml/fonts/tex/tex.js',
|
|
'mathjax-full/components/src/ui/safe/safe.js',
|
|
'mathjax-full/components/src/ui/menu/menu.js',
|
|
'mathjax-full/components/src/a11y/assistive-mml/assistive-mml.js',
|
|
'mathjax-full/components/src/startup/startup.js',
|
|
],
|
|
},
|
|
define: {
|
|
'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion),
|
|
'import.meta.env.VITE_COMMIT_REF': JSON.stringify(displayCommitRef),
|
|
'process.version': JSON.stringify(''),
|
|
global: 'globalThis',
|
|
__dirname: '""',
|
|
},
|
|
});
|