You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webpack.config.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. const fastGlob = require('fast-glob');
  2. const wrapAnsi = require('wrap-ansi');
  3. const AddAssetPlugin = require('add-asset-webpack-plugin');
  4. const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
  5. const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
  6. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  7. const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
  8. const TerserPlugin = require('terser-webpack-plugin');
  9. const VueLoaderPlugin = require('vue-loader/lib/plugin');
  10. const {statSync} = require('fs');
  11. const {resolve, parse} = require('path');
  12. const {LicenseWebpackPlugin} = require('license-webpack-plugin');
  13. const {SourceMapDevToolPlugin} = require('webpack');
  14. const glob = (pattern) => fastGlob.sync(pattern, {cwd: __dirname, absolute: true});
  15. const themes = {};
  16. for (const path of glob('web_src/less/themes/*.less')) {
  17. themes[parse(path).name] = [path];
  18. }
  19. const isProduction = process.env.NODE_ENV !== 'development';
  20. const filterCssImport = (url, ...args) => {
  21. const cssFile = args[1] || args[0]; // resourcePath is 2nd argument for url and 3rd for import
  22. const importedFile = url.replace(/[?#].+/, '').toLowerCase();
  23. if (cssFile.includes('fomantic')) {
  24. if (/brand-icons/.test(importedFile)) return false;
  25. if (/(eot|ttf|otf|woff|svg)$/.test(importedFile)) return false;
  26. }
  27. if (cssFile.includes('font-awesome')) {
  28. if (/(eot|ttf|otf|woff|svg)$/.test(importedFile)) return false;
  29. }
  30. return true;
  31. };
  32. module.exports = {
  33. mode: isProduction ? 'production' : 'development',
  34. entry: {
  35. index: [
  36. resolve(__dirname, 'web_src/js/jquery.js'),
  37. resolve(__dirname, 'web_src/fomantic/build/semantic.js'),
  38. resolve(__dirname, 'web_src/js/index.js'),
  39. resolve(__dirname, 'web_src/fomantic/build/semantic.css'),
  40. resolve(__dirname, 'web_src/less/index.less'),
  41. ],
  42. swagger: [
  43. resolve(__dirname, 'web_src/js/standalone/swagger.js'),
  44. resolve(__dirname, 'web_src/less/standalone/swagger.less'),
  45. ],
  46. serviceworker: [
  47. resolve(__dirname, 'web_src/js/serviceworker.js'),
  48. ],
  49. 'eventsource.sharedworker': [
  50. resolve(__dirname, 'web_src/js/features/eventsource.sharedworker.js'),
  51. ],
  52. 'easymde': [
  53. resolve(__dirname, 'web_src/js/easymde.js'),
  54. resolve(__dirname, 'node_modules/easymde/dist/easymde.min.css'),
  55. ],
  56. ...themes,
  57. },
  58. devtool: false,
  59. output: {
  60. path: resolve(__dirname, 'public'),
  61. filename: ({chunk}) => {
  62. // serviceworker can only manage assets below it's script's directory so
  63. // we have to put it in / instead of /js/
  64. return chunk.name === 'serviceworker' ? '[name].js' : 'js/[name].js';
  65. },
  66. chunkFilename: 'js/[name].js',
  67. },
  68. optimization: {
  69. minimize: isProduction,
  70. minimizer: [
  71. new TerserPlugin({
  72. sourceMap: true,
  73. extractComments: false,
  74. terserOptions: {
  75. output: {
  76. comments: false,
  77. },
  78. },
  79. }),
  80. new CssMinimizerPlugin({
  81. sourceMap: true,
  82. minimizerOptions: {
  83. preset: [
  84. 'default',
  85. {
  86. discardComments: {
  87. removeAll: true,
  88. },
  89. },
  90. ],
  91. },
  92. }),
  93. ],
  94. splitChunks: {
  95. chunks: 'async',
  96. name: (_, chunks) => chunks.map((item) => item.name).join('-'),
  97. },
  98. },
  99. module: {
  100. rules: [
  101. {
  102. test: /\.vue$/,
  103. exclude: /node_modules/,
  104. loader: 'vue-loader',
  105. },
  106. {
  107. test: /\.worker\.js$/,
  108. exclude: /monaco/,
  109. use: [
  110. {
  111. loader: 'worker-loader',
  112. options: {
  113. inline: 'no-fallback',
  114. },
  115. },
  116. ],
  117. },
  118. {
  119. test: /\.js$/,
  120. exclude: /node_modules/,
  121. use: [
  122. {
  123. loader: 'babel-loader',
  124. options: {
  125. sourceMaps: true,
  126. cacheDirectory: true,
  127. cacheCompression: false,
  128. cacheIdentifier: [
  129. resolve(__dirname, 'package.json'),
  130. resolve(__dirname, 'package-lock.json'),
  131. resolve(__dirname, 'webpack.config.js'),
  132. ].map((path) => statSync(path).mtime.getTime()).join(':'),
  133. presets: [
  134. [
  135. '@babel/preset-env',
  136. {
  137. useBuiltIns: 'usage',
  138. corejs: 3,
  139. },
  140. ],
  141. ],
  142. plugins: [
  143. [
  144. '@babel/plugin-transform-runtime',
  145. {
  146. regenerator: true,
  147. }
  148. ],
  149. ],
  150. generatorOpts: {
  151. compact: false,
  152. },
  153. },
  154. },
  155. ],
  156. },
  157. {
  158. test: /.css$/i,
  159. use: [
  160. {
  161. loader: MiniCssExtractPlugin.loader,
  162. },
  163. {
  164. loader: 'css-loader',
  165. options: {
  166. sourceMap: true,
  167. url: filterCssImport,
  168. import: filterCssImport,
  169. },
  170. },
  171. ],
  172. },
  173. {
  174. test: /.less$/i,
  175. use: [
  176. {
  177. loader: MiniCssExtractPlugin.loader,
  178. },
  179. {
  180. loader: 'css-loader',
  181. options: {
  182. sourceMap: true,
  183. importLoaders: 1,
  184. url: filterCssImport,
  185. import: filterCssImport,
  186. },
  187. },
  188. {
  189. loader: 'less-loader',
  190. options: {
  191. sourceMap: true,
  192. },
  193. },
  194. ],
  195. },
  196. {
  197. test: /\.svg$/,
  198. include: resolve(__dirname, 'public/img/svg'),
  199. use: [
  200. {
  201. loader: 'raw-loader',
  202. },
  203. ],
  204. },
  205. {
  206. test: /\.(ttf|woff2?)$/,
  207. use: [
  208. {
  209. loader: 'file-loader',
  210. options: {
  211. name: '[name].[ext]',
  212. outputPath: 'fonts/',
  213. publicPath: (url) => `../fonts/${url}`, // required to remove css/ path segment
  214. },
  215. },
  216. ],
  217. },
  218. {
  219. test: /\.png$/i,
  220. use: [
  221. {
  222. loader: 'file-loader',
  223. options: {
  224. name: '[name].[ext]',
  225. outputPath: 'img/webpack/',
  226. publicPath: (url) => `../img/webpack/${url}`, // required to remove css/ path segment
  227. },
  228. },
  229. ],
  230. },
  231. ],
  232. },
  233. plugins: [
  234. new VueLoaderPlugin(),
  235. // avoid generating useless js output files for css--only chunks
  236. new FixStyleOnlyEntriesPlugin({
  237. extensions: ['less', 'scss', 'css'],
  238. silent: true,
  239. }),
  240. new MiniCssExtractPlugin({
  241. filename: 'css/[name].css',
  242. chunkFilename: 'css/[name].css',
  243. }),
  244. new SourceMapDevToolPlugin({
  245. filename: '[file].map',
  246. include: [
  247. 'js/index.js',
  248. 'css/index.css',
  249. ],
  250. }),
  251. new MonacoWebpackPlugin({
  252. filename: 'js/monaco-[name].worker.js',
  253. }),
  254. isProduction ? new LicenseWebpackPlugin({
  255. outputFilename: 'js/licenses.txt',
  256. perChunkOutput: false,
  257. addBanner: false,
  258. skipChildCompilers: true,
  259. modulesDirectories: [
  260. resolve(__dirname, 'node_modules'),
  261. ],
  262. additionalModules: [
  263. '@primer/octicons',
  264. ].map((name) => ({name, directory: resolve(__dirname, `node_modules/${name}`)})),
  265. renderLicenses: (modules) => {
  266. const line = '-'.repeat(80);
  267. return modules.map((module) => {
  268. const {name, version} = module.packageJson;
  269. const {licenseId, licenseText} = module;
  270. const body = wrapAnsi(licenseText || '', 80);
  271. return `${line}\n${name}@${version} - ${licenseId}\n${line}\n${body}`;
  272. }).join('\n');
  273. },
  274. stats: {
  275. warnings: false,
  276. errors: true,
  277. },
  278. }) : new AddAssetPlugin('js/licenses.txt', `Licenses are disabled during development`),
  279. ],
  280. performance: {
  281. hints: false,
  282. maxEntrypointSize: Infinity,
  283. maxAssetSize: Infinity,
  284. },
  285. resolve: {
  286. symlinks: false,
  287. alias: {
  288. vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only
  289. },
  290. },
  291. watchOptions: {
  292. ignored: [
  293. 'node_modules/**',
  294. ],
  295. },
  296. stats: {
  297. children: false,
  298. excludeAssets: [
  299. // exclude monaco's language chunks in stats output for brevity
  300. // https://github.com/microsoft/monaco-editor-webpack-plugin/issues/113
  301. /^js\/[0-9]+\.js$/,
  302. ],
  303. },
  304. };