0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-01-30 08:10:02 +01:00
gitea/webpack.config.ts
silverwind 5925433fe6
Update JS dependencies, adjust webpack config, misc fixes (#36431)
1. Upgrade to [jQuery
4.0](https://blog.jquery.com/2026/01/17/jquery-4-0-0/). Two of the
removed APIs are in use by fomantic, but there are [polyfills
present](a3a3e581aa/web_src/fomantic/build/components/dropdown.js (L15-L17))
so it continues to work.
2. Remove manual naming of webpack chunks. I was running into below
webpack error and I see no reason for this manual chunk naming which is
prone to naming collisions. Also, the webpack build now shows all output
assets. This change will result in longer asset filenames, but webpack
should now be able to guarentee that the names are without collisions.
    ````
    ERROR in SplitChunksPlugin
    Cache group "defaultVendors" conflicts with existing chunk.
Both have the same name "--------" and existing chunk is not a parent of
the selected modules.
Use a different name for the cache group or make sure that the existing
chunk is a parent (e. g. via dependOn).
    HINT: You can omit "name" to automatically create a name.
BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as
splitChunk. This is no longer allowed when the entrypoint is not a
parent of the selected modules.
Remove this entrypoint and add modules to cache group's 'test' instead.
If you need modules to be evaluated on startup, add them to the existing
entrypoints (make them arrays). See migration guide of more info.
3. Fix test issue related to `p > div` which is invalid as per HTML spec
because `div` is not [phrasing
content](https://html.spec.whatwg.org/multipage/dom.html#phrasing-content-2)
and therefor can not be a descendant of `p`. This is related to
https://github.com/capricorn86/happy-dom/pull/2007.
4. Add webpack globals
5. Remove obsolete docs glob
6. fix security issue for `seroval` package
7. disable [vitest isolate](https://vitest.dev/config/isolate.html) for
30% faster JS tests, which are all pure.
2026-01-24 07:35:46 +00:00

272 lines
8.5 KiB
TypeScript

import wrapAnsi from 'wrap-ansi';
import AddAssetPlugin from 'add-asset-webpack-plugin';
import LicenseCheckerWebpackPlugin from '@techknowlogick/license-checker-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
import {VueLoaderPlugin} from 'vue-loader';
import EsBuildLoader from 'esbuild-loader';
import {parse} from 'node:path';
import webpack, {type Configuration, type EntryObject} from 'webpack';
import {fileURLToPath} from 'node:url';
import {readFileSync, globSync} from 'node:fs';
import {env} from 'node:process';
import tailwindcss from 'tailwindcss';
import tailwindConfig from './tailwind.config.ts';
const {EsbuildPlugin} = EsBuildLoader;
const {SourceMapDevToolPlugin, DefinePlugin, EnvironmentPlugin} = webpack;
const formatLicenseText = (licenseText: string) => wrapAnsi(licenseText || '', 80).trim();
const themes: EntryObject = {};
for (const path of globSync('web_src/css/themes/*.css', {cwd: import.meta.dirname})) {
themes[parse(path).name] = [`./${path}`];
}
const isProduction = env.NODE_ENV !== 'development';
// ENABLE_SOURCEMAP accepts the following values:
// true - all enabled, the default in development
// reduced - minimal sourcemaps, the default in production
// false - all disabled
let sourceMaps;
if ('ENABLE_SOURCEMAP' in env) {
sourceMaps = ['true', 'false'].includes(env.ENABLE_SOURCEMAP || '') ? env.ENABLE_SOURCEMAP : 'reduced';
} else {
sourceMaps = isProduction ? 'reduced' : 'true';
}
// define which web components we use for Vue to not interpret them as Vue components
const webComponents = new Set([
// our own, in web_src/js/webcomponents
'overflow-menu',
'origin-url',
'absolute-date',
// from dependencies
'markdown-toolbar',
'relative-time',
'text-expander',
]);
const filterCssImport = (url: string, ...args: Array<any>) => {
const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import
const importedFile = url.replace(/[?#].+/, '').toLowerCase();
if (cssFile.includes('fomantic')) {
if (importedFile.includes('brand-icons')) return false;
if (/(eot|ttf|otf|woff|svg)$/i.test(importedFile)) return false;
}
if (cssFile.includes('katex') && /(ttf|woff)$/i.test(importedFile)) {
return false;
}
return true;
};
export default {
mode: isProduction ? 'production' : 'development',
entry: {
index: [
fileURLToPath(new URL('web_src/js/index.ts', import.meta.url)),
fileURLToPath(new URL('web_src/fomantic/build/fomantic.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/index.css', import.meta.url)),
],
swagger: [
fileURLToPath(new URL('web_src/js/standalone/swagger.ts', import.meta.url)),
fileURLToPath(new URL('web_src/css/standalone/swagger.css', import.meta.url)),
],
'external-render-iframe': [
fileURLToPath(new URL('web_src/js/standalone/external-render-iframe.ts', import.meta.url)),
fileURLToPath(new URL('web_src/css/standalone/external-render-iframe.css', import.meta.url)),
],
'eventsource.sharedworker': [
fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.ts', import.meta.url)),
],
...(!isProduction && {
devtest: [
fileURLToPath(new URL('web_src/js/standalone/devtest.ts', import.meta.url)),
fileURLToPath(new URL('web_src/css/standalone/devtest.css', import.meta.url)),
],
}),
...themes,
},
devtool: false,
output: {
path: fileURLToPath(new URL('public/assets', import.meta.url)),
filename: 'js/[name].js',
chunkFilename: 'js/[name].[contenthash:8].js',
},
optimization: {
minimize: isProduction,
minimizer: [
new EsbuildPlugin({
target: 'es2020',
minify: true,
css: true,
legalComments: 'none',
}),
],
moduleIds: 'named',
chunkIds: 'named',
},
module: {
rules: [
{
test: /\.vue$/i,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
compilerOptions: {
isCustomElement: (tag: string) => webComponents.has(tag),
},
},
},
{
test: /\.js$/i,
exclude: /node_modules/,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'js',
target: 'es2020',
},
},
],
},
{
test: /\.ts$/i,
exclude: /node_modules/,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'ts',
target: 'es2020',
},
},
],
},
{
test: /\.css$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
sourceMap: sourceMaps === 'true',
url: {filter: filterCssImport},
import: {filter: filterCssImport},
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
tailwindcss(tailwindConfig),
],
},
},
},
],
},
{
test: /\.svg$/i,
include: fileURLToPath(new URL('public/assets/img/svg', import.meta.url)),
type: 'asset/source',
},
{
test: /\.(ttf|woff2?)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[contenthash:8][ext]',
},
},
],
},
plugins: [
new DefinePlugin({
__VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API
__VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // https://github.com/vuejs/vue-cli/pull/7443
}),
// all environment variables used in bundled js via process.env must be declared here
new EnvironmentPlugin({
TEST: 'false',
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: 'css/[name].[contenthash:8].css',
}),
sourceMaps !== 'false' && new SourceMapDevToolPlugin({
filename: '[file].[contenthash:8].map',
...(sourceMaps === 'reduced' && {include: /^js\/index\.js$/}),
}),
new MonacoWebpackPlugin({
filename: 'js/monaco-[name].[contenthash:8].worker.js',
}),
isProduction ? new LicenseCheckerWebpackPlugin({
outputFilename: 'licenses.txt',
outputWriter: ({dependencies}: {dependencies: Array<Record<string, string>>}) => {
const line = '-'.repeat(80);
const goJson = readFileSync('assets/go-licenses.json', 'utf8');
const goModules = JSON.parse(goJson).map(({name, licenseText}: Record<string, string>) => {
return {name, body: formatLicenseText(licenseText)};
});
const jsModules = dependencies.map(({name, version, licenseName, licenseText}) => {
return {name, version, licenseName, body: formatLicenseText(licenseText)};
});
const modules = [...goModules, ...jsModules].sort((a, b) => a.name.localeCompare(b.name));
return modules.map(({name, version, licenseName, body}) => {
const title = licenseName ? `${name}@${version} - ${licenseName}` : name;
return `${line}\n${title}\n${line}\n${body}`;
}).join('\n');
},
override: {
'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33
},
emitError: true,
allow: '(Apache-2.0 OR 0BSD OR BSD-2-Clause OR BSD-3-Clause OR MIT OR ISC OR CPAL-1.0 OR Unlicense OR EPL-1.0 OR EPL-2.0)',
}) : new AddAssetPlugin('licenses.txt', `Licenses are disabled during development`),
],
performance: {
hints: false,
maxEntrypointSize: Infinity,
maxAssetSize: Infinity,
},
resolve: {
symlinks: true,
modules: ['node_modules'],
},
watchOptions: {
ignored: [
'node_modules/**',
],
},
stats: {
assetsSort: 'name',
assetsSpace: Infinity,
cached: false,
cachedModules: false,
children: false,
chunkModules: false,
chunkOrigins: false,
chunksSort: 'name',
colors: true,
entrypoints: false,
groupAssetsByChunk: false,
groupAssetsByEmitStatus: false,
groupAssetsByInfo: false,
groupModulesByAttributes: false,
modules: false,
reasons: false,
runtimeModules: false,
},
} satisfies Configuration;