mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-14 03:29:55 +00:00
Merge some standalone Vite entries into index.js (#37085)
Keep `swagger` and `external-render-helper` as a standalone entries for external render. - Move `devtest.ts` to `modules/` as init functions - Make external renders correctly load its helper JS and Gitea's current theme - Make external render iframe inherit Gitea's iframe's background color to avoid flicker - Add e2e tests for external render and OpenAPI iframe --------- Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
+44
-30
@@ -2,13 +2,14 @@ import {build, defineConfig} from 'vite';
|
||||
import vuePlugin from '@vitejs/plugin-vue';
|
||||
import {stringPlugin} from 'vite-string-plugin';
|
||||
import {readFileSync, writeFileSync, mkdirSync, unlinkSync, globSync} from 'node:fs';
|
||||
import path, {join, parse} from 'node:path';
|
||||
import path, {basename, join, parse} from 'node:path';
|
||||
import {env} from 'node:process';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import tailwindConfig from './tailwind.config.ts';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
import licensePlugin from 'rollup-plugin-license';
|
||||
import type {InlineConfig, Plugin, Rolldown} from 'vite';
|
||||
import {camelize} from 'vue';
|
||||
|
||||
const isProduction = env.NODE_ENV !== 'development';
|
||||
|
||||
@@ -76,13 +77,14 @@ function commonViteOpts({build, ...other}: InlineConfig): InlineConfig {
|
||||
};
|
||||
}
|
||||
|
||||
const iifeEntry = join(import.meta.dirname, 'web_src/js/iife.ts');
|
||||
|
||||
function iifeBuildOpts({entryFileNames, write}: {entryFileNames: string, write?: boolean}) {
|
||||
function iifeBuildOpts({sourceFileName, write}: {sourceFileName: string, write?: boolean}) {
|
||||
const sourceBaseName = basename(sourceFileName, '.ts');
|
||||
// HINT: VITE-OUTPUT-DIR: all outputted JS files are in "js" directory
|
||||
const entryFileName = `js/${sourceBaseName}.[hash:8].js`;
|
||||
return commonViteOpts({
|
||||
build: {
|
||||
lib: {entry: iifeEntry, formats: ['iife'], name: 'iife'},
|
||||
rolldownOptions: {output: {entryFileNames}},
|
||||
lib: {entry: join(import.meta.dirname, 'web_src/js', sourceFileName), name: camelize(sourceBaseName), formats: ['iife']},
|
||||
rolldownOptions: {output: {entryFileNames: entryFileName}},
|
||||
...(write === false && {write: false}),
|
||||
},
|
||||
plugins: [stringPlugin()],
|
||||
@@ -91,19 +93,20 @@ function iifeBuildOpts({entryFileNames, write}: {entryFileNames: string, write?:
|
||||
|
||||
// Build iife.js as a blocking IIFE bundle. In dev mode, serves it from memory
|
||||
// and rebuilds on file changes. In prod mode, writes to disk during closeBundle.
|
||||
function iifePlugin(): Plugin {
|
||||
let iifeCode = '';
|
||||
let iifeMap = '';
|
||||
function iifePlugin(sourceFileName: string): Plugin {
|
||||
let iifeCode = '', iifeMap = '';
|
||||
const iifeModules = new Set<string>();
|
||||
let isBuilding = false;
|
||||
|
||||
const sourceBaseName = path.basename(sourceFileName, '.ts');
|
||||
return {
|
||||
name: 'iife',
|
||||
name: `iife:${sourceFileName}`, // plugin name
|
||||
async configureServer(server) {
|
||||
const buildAndCache = async () => {
|
||||
const result = await build(iifeBuildOpts({entryFileNames: 'js/iife.js', write: false}));
|
||||
const result = await build(iifeBuildOpts({sourceFileName, write: false}));
|
||||
const output = (Array.isArray(result) ? result[0] : result) as Rolldown.RolldownOutput;
|
||||
const chunk = output.output[0];
|
||||
iifeCode = chunk.code.replace(/\/\/# sourceMappingURL=.*/, '//# sourceMappingURL=__vite_iife.js.map');
|
||||
iifeCode = chunk.code.replace(/\/\/# sourceMappingURL=.*/, `//# sourceMappingURL=${sourceBaseName}.js.map`);
|
||||
const mapAsset = output.output.find((o) => o.fileName.endsWith('.map'));
|
||||
iifeMap = mapAsset && 'source' in mapAsset ? String(mapAsset.source) : '';
|
||||
iifeModules.clear();
|
||||
@@ -129,15 +132,15 @@ function iifePlugin(): Plugin {
|
||||
});
|
||||
|
||||
server.middlewares.use((req, res, next) => {
|
||||
// "__vite_iife" is a virtual file in memory, serve it directly
|
||||
// on the dev server, an "iife" file is a virtual file in memory, serve it directly
|
||||
const pathname = req.url!.split('?')[0];
|
||||
if (pathname === '/web_src/js/__vite_dev_server_check') {
|
||||
res.end('ok');
|
||||
} else if (pathname === '/web_src/js/__vite_iife.js') {
|
||||
} else if (pathname === `/web_src/js/${sourceFileName}`) {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
res.end(iifeCode);
|
||||
} else if (pathname === '/web_src/js/__vite_iife.js.map') {
|
||||
} else if (pathname === `/web_src/js/${sourceBaseName}.js.map`) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
res.end(iifeMap);
|
||||
@@ -147,29 +150,38 @@ function iifePlugin(): Plugin {
|
||||
});
|
||||
},
|
||||
async closeBundle() {
|
||||
for (const file of globSync('js/iife.*.js*', {cwd: outDir})) unlinkSync(join(outDir, file));
|
||||
const result = await build(iifeBuildOpts({entryFileNames: 'js/iife.[hash:8].js'}));
|
||||
for (const file of globSync(`js/${sourceBaseName}.*.js*`, {cwd: outDir})) unlinkSync(join(outDir, file));
|
||||
|
||||
const result = await build(iifeBuildOpts({sourceFileName}));
|
||||
const buildOutput = (Array.isArray(result) ? result[0] : result) as Rolldown.RolldownOutput;
|
||||
const entry = buildOutput.output.find((o) => o.fileName.startsWith('js/iife.'));
|
||||
const entry = buildOutput.output.find((o) => o.fileName.startsWith(`js/${sourceBaseName}.`));
|
||||
if (!entry) throw new Error('IIFE build produced no output');
|
||||
|
||||
const manifestPath = join(outDir, '.vite', 'manifest.json');
|
||||
writeFileSync(manifestPath, JSON.stringify({
|
||||
...JSON.parse(readFileSync(manifestPath, 'utf8')),
|
||||
'web_src/js/iife.ts': {file: entry.fileName, name: 'iife', isEntry: true},
|
||||
}, null, 2));
|
||||
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
manifestData[`web_src/js/${sourceFileName}`] = {file: entry.fileName, name: sourceBaseName, isEntry: true};
|
||||
writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// In reduced sourcemap mode, only keep sourcemaps for main files
|
||||
function reducedSourcemapPlugin(): Plugin {
|
||||
const standalonePrefixes = [
|
||||
'js/index.',
|
||||
'js/iife.',
|
||||
'js/swagger.',
|
||||
'js/external-render-helper.',
|
||||
'js/eventsource.sharedworker.',
|
||||
];
|
||||
return {
|
||||
name: 'reduced-sourcemap',
|
||||
apply: 'build',
|
||||
closeBundle() {
|
||||
if (enableSourcemap !== 'reduced') return;
|
||||
for (const file of globSync('{js,css}/*.map', {cwd: outDir})) {
|
||||
if (!file.startsWith('js/index.') && !file.startsWith('js/iife.')) unlinkSync(join(outDir, file));
|
||||
if (standalonePrefixes.some((prefix) => file.startsWith(prefix))) continue;
|
||||
unlinkSync(join(outDir, file));
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -215,6 +227,7 @@ export default defineConfig(commonViteOpts({
|
||||
open: false,
|
||||
host: '0.0.0.0',
|
||||
strictPort: false,
|
||||
cors: true,
|
||||
fs: {
|
||||
// VITE-DEV-SERVER-SECURITY: the dev server will be exposed to public by Gitea's web server, so we need to strictly limit the access
|
||||
// Otherwise `/@fs/*` will be able to access any file (including app.ini which contains INTERNAL_TOKEN)
|
||||
@@ -245,15 +258,15 @@ export default defineConfig(commonViteOpts({
|
||||
rolldownOptions: {
|
||||
input: {
|
||||
index: join(import.meta.dirname, 'web_src/js/index.ts'),
|
||||
swagger: join(import.meta.dirname, 'web_src/js/standalone/swagger.ts'),
|
||||
'external-render-iframe': join(import.meta.dirname, 'web_src/js/standalone/external-render-iframe.ts'),
|
||||
'eventsource.sharedworker': join(import.meta.dirname, 'web_src/js/features/eventsource.sharedworker.ts'),
|
||||
...(!isProduction && {
|
||||
devtest: join(import.meta.dirname, 'web_src/js/standalone/devtest.ts'),
|
||||
}),
|
||||
swagger: join(import.meta.dirname, 'web_src/js/swagger.ts'),
|
||||
'eventsource.sharedworker': join(import.meta.dirname, 'web_src/js/eventsource.sharedworker.ts'),
|
||||
devtest: join(import.meta.dirname, 'web_src/css/devtest.css'),
|
||||
...themes,
|
||||
},
|
||||
output: {
|
||||
// HINT: VITE-OUTPUT-DIR: all outputted JS files are in "js" directory
|
||||
// So standalone/iife source files should also be in "js" directory,
|
||||
// to keep consistent between production and dev server, avoid unexpected behaviors.
|
||||
entryFileNames: 'js/[name].[hash:8].js',
|
||||
chunkFileNames: 'js/[name].[hash:8].js',
|
||||
assetFileNames: ({names}) => {
|
||||
@@ -287,7 +300,8 @@ export default defineConfig(commonViteOpts({
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
|
||||
},
|
||||
plugins: [
|
||||
iifePlugin(),
|
||||
iifePlugin('iife.ts'),
|
||||
iifePlugin('external-render-helper.ts'),
|
||||
viteDevServerPortPlugin(),
|
||||
reducedSourcemapPlugin(),
|
||||
filterCssUrlPlugin(),
|
||||
|
||||
Reference in New Issue
Block a user