gitea源码

webpack.config.ts 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import wrapAnsi from 'wrap-ansi';
  2. import AddAssetPlugin from 'add-asset-webpack-plugin';
  3. import LicenseCheckerWebpackPlugin from '@techknowlogick/license-checker-webpack-plugin';
  4. import MiniCssExtractPlugin from 'mini-css-extract-plugin';
  5. import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
  6. import {VueLoaderPlugin} from 'vue-loader';
  7. import EsBuildLoader from 'esbuild-loader';
  8. import {parse} from 'node:path';
  9. import webpack, {type Configuration, type EntryObject} from 'webpack';
  10. import {fileURLToPath} from 'node:url';
  11. import {readFileSync, globSync} from 'node:fs';
  12. import {env} from 'node:process';
  13. import tailwindcss from 'tailwindcss';
  14. import tailwindConfig from './tailwind.config.ts';
  15. const {EsbuildPlugin} = EsBuildLoader;
  16. const {SourceMapDevToolPlugin, DefinePlugin, EnvironmentPlugin} = webpack;
  17. const formatLicenseText = (licenseText: string) => wrapAnsi(licenseText || '', 80).trim();
  18. const themes: EntryObject = {};
  19. for (const path of globSync('web_src/css/themes/*.css', {cwd: import.meta.dirname})) {
  20. themes[parse(path).name] = [`./${path}`];
  21. }
  22. const isProduction = env.NODE_ENV !== 'development';
  23. // ENABLE_SOURCEMAP accepts the following values:
  24. // true - all enabled, the default in development
  25. // reduced - minimal sourcemaps, the default in production
  26. // false - all disabled
  27. let sourceMaps;
  28. if ('ENABLE_SOURCEMAP' in env) {
  29. sourceMaps = ['true', 'false'].includes(env.ENABLE_SOURCEMAP) ? env.ENABLE_SOURCEMAP : 'reduced';
  30. } else {
  31. sourceMaps = isProduction ? 'reduced' : 'true';
  32. }
  33. // define which web components we use for Vue to not interpret them as Vue components
  34. const webComponents = new Set([
  35. // our own, in web_src/js/webcomponents
  36. 'overflow-menu',
  37. 'origin-url',
  38. 'absolute-date',
  39. // from dependencies
  40. 'markdown-toolbar',
  41. 'relative-time',
  42. 'text-expander',
  43. ]);
  44. const filterCssImport = (url: string, ...args: Array<any>) => {
  45. const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import
  46. const importedFile = url.replace(/[?#].+/, '').toLowerCase();
  47. if (cssFile.includes('fomantic')) {
  48. if (importedFile.includes('brand-icons')) return false;
  49. if (/(eot|ttf|otf|woff|svg)$/i.test(importedFile)) return false;
  50. }
  51. if (cssFile.includes('katex') && /(ttf|woff)$/i.test(importedFile)) {
  52. return false;
  53. }
  54. return true;
  55. };
  56. export default {
  57. mode: isProduction ? 'production' : 'development',
  58. entry: {
  59. index: [
  60. fileURLToPath(new URL('web_src/js/index.ts', import.meta.url)),
  61. fileURLToPath(new URL('web_src/fomantic/build/fomantic.css', import.meta.url)),
  62. fileURLToPath(new URL('web_src/css/index.css', import.meta.url)),
  63. ],
  64. swagger: [
  65. fileURLToPath(new URL('web_src/js/standalone/swagger.ts', import.meta.url)),
  66. fileURLToPath(new URL('web_src/css/standalone/swagger.css', import.meta.url)),
  67. ],
  68. 'eventsource.sharedworker': [
  69. fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.ts', import.meta.url)),
  70. ],
  71. ...(!isProduction && {
  72. devtest: [
  73. fileURLToPath(new URL('web_src/js/standalone/devtest.ts', import.meta.url)),
  74. fileURLToPath(new URL('web_src/css/standalone/devtest.css', import.meta.url)),
  75. ],
  76. }),
  77. ...themes,
  78. },
  79. devtool: false,
  80. output: {
  81. path: fileURLToPath(new URL('public/assets', import.meta.url)),
  82. filename: () => 'js/[name].js',
  83. chunkFilename: ({chunk}) => {
  84. const language = (/monaco.*languages?_.+?_(.+?)_/.exec(String(chunk.id)) || [])[1];
  85. return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`;
  86. },
  87. },
  88. optimization: {
  89. minimize: isProduction,
  90. minimizer: [
  91. new EsbuildPlugin({
  92. target: 'es2020',
  93. minify: true,
  94. css: true,
  95. legalComments: 'none',
  96. }),
  97. ],
  98. splitChunks: {
  99. chunks: 'async',
  100. name: (_, chunks) => chunks.map((item) => item.name).join('-'),
  101. },
  102. moduleIds: 'named',
  103. chunkIds: 'named',
  104. },
  105. module: {
  106. rules: [
  107. {
  108. test: /\.vue$/i,
  109. exclude: /node_modules/,
  110. loader: 'vue-loader',
  111. options: {
  112. compilerOptions: {
  113. isCustomElement: (tag: string) => webComponents.has(tag),
  114. },
  115. },
  116. },
  117. {
  118. test: /\.js$/i,
  119. exclude: /node_modules/,
  120. use: [
  121. {
  122. loader: 'esbuild-loader',
  123. options: {
  124. loader: 'js',
  125. target: 'es2020',
  126. },
  127. },
  128. ],
  129. },
  130. {
  131. test: /\.ts$/i,
  132. exclude: /node_modules/,
  133. use: [
  134. {
  135. loader: 'esbuild-loader',
  136. options: {
  137. loader: 'ts',
  138. target: 'es2020',
  139. },
  140. },
  141. ],
  142. },
  143. {
  144. test: /\.css$/i,
  145. use: [
  146. {
  147. loader: MiniCssExtractPlugin.loader,
  148. },
  149. {
  150. loader: 'css-loader',
  151. options: {
  152. sourceMap: sourceMaps === 'true',
  153. url: {filter: filterCssImport},
  154. import: {filter: filterCssImport},
  155. importLoaders: 1,
  156. },
  157. },
  158. {
  159. loader: 'postcss-loader',
  160. options: {
  161. postcssOptions: {
  162. plugins: [
  163. tailwindcss(tailwindConfig),
  164. ],
  165. },
  166. },
  167. },
  168. ],
  169. },
  170. {
  171. test: /\.svg$/i,
  172. include: fileURLToPath(new URL('public/assets/img/svg', import.meta.url)),
  173. type: 'asset/source',
  174. },
  175. {
  176. test: /\.(ttf|woff2?)$/i,
  177. type: 'asset/resource',
  178. generator: {
  179. filename: 'fonts/[name].[contenthash:8][ext]',
  180. },
  181. },
  182. ],
  183. },
  184. plugins: [
  185. new DefinePlugin({
  186. __VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API
  187. __VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
  188. __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // https://github.com/vuejs/vue-cli/pull/7443
  189. }),
  190. // all environment variables used in bundled js via process.env must be declared here
  191. new EnvironmentPlugin({
  192. TEST: 'false',
  193. }),
  194. new VueLoaderPlugin(),
  195. new MiniCssExtractPlugin({
  196. filename: 'css/[name].css',
  197. chunkFilename: 'css/[name].[contenthash:8].css',
  198. }),
  199. sourceMaps !== 'false' && new SourceMapDevToolPlugin({
  200. filename: '[file].[contenthash:8].map',
  201. ...(sourceMaps === 'reduced' && {include: /^js\/index\.js$/}),
  202. }),
  203. new MonacoWebpackPlugin({
  204. filename: 'js/monaco-[name].[contenthash:8].worker.js',
  205. }),
  206. isProduction ? new LicenseCheckerWebpackPlugin({
  207. outputFilename: 'licenses.txt',
  208. outputWriter: ({dependencies}: {dependencies: Array<Record<string, string>>}) => {
  209. const line = '-'.repeat(80);
  210. const goJson = readFileSync('assets/go-licenses.json', 'utf8');
  211. const goModules = JSON.parse(goJson).map(({name, licenseText}: Record<string, string>) => {
  212. return {name, body: formatLicenseText(licenseText)};
  213. });
  214. const jsModules = dependencies.map(({name, version, licenseName, licenseText}) => {
  215. return {name, version, licenseName, body: formatLicenseText(licenseText)};
  216. });
  217. const modules = [...goModules, ...jsModules].sort((a, b) => a.name.localeCompare(b.name));
  218. return modules.map(({name, version, licenseName, body}) => {
  219. const title = licenseName ? `${name}@${version} - ${licenseName}` : name;
  220. return `${line}\n${title}\n${line}\n${body}`;
  221. }).join('\n');
  222. },
  223. override: {
  224. 'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33
  225. },
  226. emitError: true,
  227. 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)',
  228. }) : new AddAssetPlugin('licenses.txt', `Licenses are disabled during development`),
  229. ],
  230. performance: {
  231. hints: false,
  232. maxEntrypointSize: Infinity,
  233. maxAssetSize: Infinity,
  234. },
  235. resolve: {
  236. symlinks: true,
  237. modules: ['node_modules'],
  238. },
  239. watchOptions: {
  240. ignored: [
  241. 'node_modules/**',
  242. ],
  243. },
  244. stats: {
  245. assetsSort: 'name',
  246. assetsSpace: Infinity,
  247. cached: false,
  248. cachedModules: false,
  249. children: false,
  250. chunkModules: false,
  251. chunkOrigins: false,
  252. chunksSort: 'name',
  253. colors: true,
  254. entrypoints: false,
  255. excludeAssets: [
  256. /^js\/monaco-language-.+\.js$/,
  257. !isProduction && /^licenses.txt$/,
  258. ].filter(Boolean),
  259. groupAssetsByChunk: false,
  260. groupAssetsByEmitStatus: false,
  261. groupAssetsByInfo: false,
  262. groupModulesByAttributes: false,
  263. modules: false,
  264. reasons: false,
  265. runtimeModules: false,
  266. },
  267. } satisfies Configuration;