Files
daydream/vite.config.mjs
T
2026-09-04 11:09:34 +02:00

155 lines
4.7 KiB
JavaScript

import vue from '@vitejs/plugin-vue';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
const ROOT = fileURLToPath(new URL('.', import.meta.url));
const SRC = path.resolve(ROOT, 'src');
const PUBLIC = path.resolve(ROOT, 'public');
export default defineConfig(({ mode }) => {
const isDev = (mode === 'development');
return {
base: './',
publicDir: false,
plugins: [
myThoughtsPublicAssets(),
myThoughtsLint(isDev),
vue()
],
build: {
outDir: PUBLIC,
assetsDir: 'assets',
emptyOutDir: false,
manifest: true,
sourcemap: isDev,
minify: !isDev,
cssMinify: !isDev,
assetsInlineLimit: 1 * 1024,
rolldownOptions: {
input: {
app: path.resolve(SRC, 'app.js')
},
output: {
entryFileNames: isDev ? 'assets/[name].js' : 'assets/[name].[hash].js',
chunkFileNames: isDev ? 'assets/[name].js' : 'assets/[name].[hash].js',
assetFileNames: (assetInfo) => {
const sourceName = assetInfo.names?.[0] || assetInfo.name || 'asset';
const extension = path.extname(sourceName).toLowerCase();
const isImage = ['.png', '.svg', '.jpg', '.jpeg', '.gif', '.webp'].includes(extension);
const isFont = ['.woff', '.woff2', '.ttf', '.otf'].includes(extension);
const folder = isImage ? 'assets/images' : (isFont ? 'assets/fonts' : 'assets');
return isDev ? `${folder}/[name][extname]` : `${folder}/[name].[hash][extname]`;
}
}
}
},
resolve: {
extensions: ['.mjs', '.js', '.vue', '.scss', '.json'],
alias: {
'@components': path.resolve(SRC, 'components'),
'@images': path.resolve(SRC, 'images'),
'@scripts': path.resolve(SRC, 'scripts'),
'@styles': path.resolve(SRC, 'styles')
}
},
define: {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
}
};
});
function myThoughtsLint(isDev) {
return {
name: 'daydream-lint',
apply: 'build',
//In `--watch` mode Vite re-runs the whole plugin pipeline (including
//buildStart) on every rebuild it triggers from a file change, so this
//alone gives re-linting on every save with no separate watchChange
//wiring needed.
async buildStart() {
await runLint(isDev);
}
};
}
async function runLint(isDev) {
const { ESLint } = await import('eslint');
const eslint = new ESLint({ cwd: ROOT });
const results = await eslint.lintFiles(['src']);
const formatter = await eslint.loadFormatter('stylish');
const output = await formatter.format(results);
if(output) process.stdout.write(output + '\n');
//Dev keeps watching regardless - the point is fast feedback, not a gate.
//Prod aborts the build so bad code can't ship.
const hasErrors = results.some((result) => result.errorCount > 0);
if(hasErrors && !isDev) {
throw new Error('ESLint found errors in src/ - aborting production build.');
}
}
function myThoughtsPublicAssets() {
return {
name: 'daydream-public-assets',
apply: 'build',
buildStart() {
cleanGeneratedAssets();
ensurePublicSymlinks();
}
};
}
//Everything under public/ except index.php is build output, so a rebuild
//starts from a clean slate and never leaves an orphaned hashed file behind.
function cleanGeneratedAssets() {
fs.rmSync(path.resolve(PUBLIC, '.vite'), { recursive: true, force: true });
const assetsDir = path.resolve(PUBLIC, 'assets');
if(fs.existsSync(assetsDir)) {
for(const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
const entryPath = path.resolve(assetsDir, entry.name);
if(entry.name === 'images' && entry.isDirectory()) cleanImagesDir(entryPath);
else fs.rmSync(entryPath, { recursive: true, force: true });
}
}
fs.mkdirSync(path.resolve(PUBLIC, 'assets', 'images'), { recursive: true });
}
//icons/ is a symlink to source files the mask references by stable path
//(favicon, webmanifest) - it must survive the sweep.
function cleanImagesDir(imagesDir) {
for(const entry of fs.readdirSync(imagesDir, { withFileTypes: true })) {
if(entry.name === 'icons') continue;
fs.rmSync(path.resolve(imagesDir, entry.name), { recursive: true, force: true });
}
}
function ensurePublicSymlinks() {
ensureSymlink('../../../src/images/icons', path.resolve(PUBLIC, 'assets', 'images', 'icons'));
}
function ensureSymlink(target, linkPath) {
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
try {
const stats = fs.lstatSync(linkPath);
if(stats.isSymbolicLink()) {
if(fs.readlinkSync(linkPath) === target) return;
fs.unlinkSync(linkPath);
}
else throw new Error(`Refusing to replace non-symlink public path: ${linkPath}`);
}
catch (error) {
if(error.code !== 'ENOENT') throw error;
}
fs.symlinkSync(target, linkPath);
}