/* eslint-disable global-require */ import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import { ESBuildMinifyPlugin } from 'esbuild-loader'; import { DuplicatesPlugin } from 'inspectpack/plugin'; import ExtractCssPlugin from 'mini-css-extract-plugin'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import { dirname } from 'path'; import webpack from 'webpack'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; import WebpackBar from 'webpackbar'; import root from '../utils/root'; export default function (env: { watch?: boolean, production?: boolean, measure?: boolean } = {}) { function esbuildLoader() { return { loader: 'esbuild-loader', options: { loader: 'tsx', target: 'es2015', sourcemap: true, }, }; } function cssLoader() { return { loader: 'css-loader', options: { importLoaders: 1 }, }; } function postcssLoader() { return { loader: 'postcss-loader', options: { postcssOptions: { sourceMap: false, config: root('postcss.config.js') } }, }; } function extractCssLoader() { return { loader: ExtractCssPlugin.loader, // FIXME auto? options: { publicPath: '', }, }; } function stylusLoader() { return { loader: 'stylus-loader', options: { stylusOptions: { preferPathResolver: 'webpack', use: [require('rupture')()], import: ['~vj/common/common.inc.styl'], }, }, }; } const config: import('webpack').Configuration = { // bail: !env.production, mode: (env.production || env.measure) ? 'production' : 'development', profile: env.measure, context: root(), stats: { preset: 'errors-warnings', }, devtool: false, entry: { hydro: './entry.js', polyfill: './polyfill.ts', 'default.theme': './theme/default.js', 'service-worker': './service-worker.ts', 'messages-shared-worker': './components/message/worker.ts', }, cache: { type: 'filesystem', cacheDirectory: root('../../.cache'), idleTimeout: 30000, buildDependencies: { config: [__filename], }, }, output: { path: root('public'), publicPath: '/', // overwrite in entry.js hashFunction: 'sha1', hashDigest: 'hex', hashDigestLength: 10, filename: '[name].js?[contenthash:6]', chunkFilename: '[name].[chunkhash:6].chunk.js', }, resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.cjs'], alias: { vj: root(), }, }, module: { rules: [ { test: /\.svg$/i, oneOf: [ { issuer: /\.[jt]sx?$/, resourceQuery: /react/, use: { loader: '@svgr/webpack', options: { icon: true } }, }, { type: 'asset/resource', }, ], }, { resourceQuery: /inline/, type: 'asset/inline', }, { test: /\.(ttf|eot|woff|woff2|png|jpg|jpeg|gif)$/, type: 'asset/resource', generator: { filename: (pathData) => { const p = pathData.module.resource.replace(/\\/g, '/'); const filename = p.split('/').pop(); if (p.includes('node_modules')) { const extra = p.split('node_modules/').pop(); const moduleName = extra.split('/')[0]; if (extra.includes('@fontsource')) { return `fonts/${filename}?[hash:6]`; } if (['katex', 'monaco-editor'].includes(moduleName)) { return `modules/${moduleName}/${filename}?[hash:6]`; } return `modules/${extra.substr(1)}?[hash:6]`; } if (p.includes('.iconfont')) return `${filename}?[hash:6]`; return `${p.split('ui-default')[1].substring(1)}?[hash:6]`; }, }, }, { test: /\.ts$/, include: /@types\//, type: 'asset/inline', generator: { dataUrl: (buf) => buf.toString(), }, }, { test: /\.[mc]?[jt]sx?$/, exclude: [/@types\//, /components\/message\//, /entry\.js/], type: 'javascript/auto', use: [esbuildLoader()], }, { test: /\.[mc]?[jt]sx?$/, include: [/components\/message\//, /entry\.js/], type: 'javascript/auto', use: [{ loader: 'ts-loader', options: { transpileOnly: true, }, }], }, { test: /\.styl$/, use: [extractCssLoader(), cssLoader(), postcssLoader(), stylusLoader()], }, { test: /\.css$/, use: [extractCssLoader(), cssLoader(), postcssLoader()], }, ], }, experiments: { asyncWebAssembly: true, syncWebAssembly: true, }, optimization: { splitChunks: { minSize: 256000, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '-', cacheGroups: { style: { priority: 99, name: 'theme', type: 'css/mini-extract', chunks: 'all', enforce: true, }, vendors: { test: /[\\/]node_modules[\\/].+\.([jt]sx?|json|yaml)$/, priority: -10, name(module) { const packageName = module.context.replace(/\\/g, '/').split('node_modules/').pop().split('/')[0]; if (packageName === 'monaco-editor-nls') { return `i.monaco.${module.userRequest.split('/').pop().split('.')[0]}`; } return `n.${packageName.replace('@', '')}`; }, reuseExistingChunk: true, }, default: { priority: -20, reuseExistingChunk: true, }, }, }, usedExports: true, minimizer: [new ESBuildMinifyPlugin({ css: true, minify: true, minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, treeShaking: true, target: [ 'chrome60', ], exclude: [/mathmaps/, /\.min\.js$/], })], moduleIds: env.production ? 'deterministic' : 'named', chunkIds: env.production ? 'deterministic' : 'named', }, plugins: [ new CleanWebpackPlugin(), new WebpackBar(), new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery', React: 'react', monaco: 'monaco-editor/esm/vs/editor/editor.api', }), new ExtractCssPlugin({ filename: '[name].css?[fullhash:6]', }), new WebpackManifestPlugin({}), new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$|mathjax|abcjs|vditor.+\.d\.ts)/ }), new CopyWebpackPlugin({ patterns: [ { from: root('static') }, { from: root('components/navigation/nav-logo-small_dark.png'), to: 'components/navigation/nav-logo-small_dark.png' }, { from: root(`${dirname(require.resolve('streamsaver/package.json'))}/mitm.html`), to: 'streamsaver/mitm.html' }, { from: root(`${dirname(require.resolve('streamsaver/package.json'))}/sw.js`), to: 'streamsaver/sw.js' }, { from: root(`${dirname(require.resolve('vditor/package.json'))}/dist`), to: 'vditor/dist' }, { from: root(`${dirname(require.resolve('graphiql/package.json'))}/graphiql.min.css`), to: 'graphiql.min.css' }, { from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' }, ], }), new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(require('@hydrooj/ui-default/package.json').version), }), new webpack.optimize.MinChunkSizePlugin({ minChunkSize: 128000, }), new webpack.NormalModuleReplacementPlugin(/\/(vscode-)?nls\.js/, require.resolve('../../components/monaco/nls')), new webpack.NormalModuleReplacementPlugin(/^prettier[$/]/, root('../../modules/nop.ts')), new MonacoWebpackPlugin({ filename: '[name].[hash:6].worker.js', customLanguages: [{ label: 'yaml', entry: require.resolve('monaco-yaml/index.js'), worker: { id: 'vs/language/yaml/yamlWorker', entry: require.resolve('monaco-yaml/yaml.worker.js'), }, }], }), ...env.measure ? [ new BundleAnalyzerPlugin({ analyzerPort: 'auto' }), new DuplicatesPlugin(), ] : [], ], }; return config; }