// SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , David Mosbach , Gregor Kleen , Sarah Vaupel , Sarah Vaupel // // SPDX-License-Identifier: AGPL-3.0-or-later const webpack = require('webpack'); const path = require('node:path'); // import { execSync } from 'node:child_process'; const tmp = require('tmp'); tmp.setGracefulCleanup(); const fs = require('fs-extra'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const yaml = require('js-yaml'); const crypto = require('crypto'); // import { version as webpackVersion } from 'webpack/package.json' assert { type: 'json' }; // version.split('.').slice(0, 2).join('.'); // import { version as packageVersion } from './package.json' assert { type: 'json' }; const webpackJson = require('webpack/package.json'); const webpackVersion = webpackJson.version.split('.').slice(0, 2).join('.'); const packageJson = require('./package.json'); const packageVersion = packageJson.version; const i18nJson = require('./config/i18n.json'); async function webpackConfig() { const wellKnownDir = 'well-known'; const wellKnownCacheDir = '.cache/well-known'; const staticDir = 'static'; const assetsDir = 'assets'; const faviconsDir = `${assetsDir}/favicons`; return { module: { rules: [ { loader: 'babel-loader', options: { plugins: ['@babel/plugin-syntax-dynamic-import'], presets: [ [ '@babel/preset-env', { modules: false, targets: { edge: "17", firefox: "50", chrome: "60", safari: "11.1", ie: "11", }, useBuiltIns: "usage", corejs: 3 } ] ] }, test: /\.js$/i, exclude: /node_modules/, }, { test: /\.(sa|sc|c)ss$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { sourceMap: true }}, { loader: 'postcss-loader', options: { sourceMap: true, postcssOptions: { plugins: [ 'postcss-preset-env', 'autoprefixer' ], }, }}, { loader: 'path.resolve-url-loader', options: { sourceMap: true }}, { loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: true }}, ], }, { test: /\.(woff(2)?|ttf|eot|svg)(\?.*)?$/i, type: 'asset' } ] }, entry: { main: [ path.resolve('frontend/src/polyfill.js'), path.resolve('frontend/src/main.js'), ] }, plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // all options are optional filename: '[chunkhash].css', chunkFilename: '[chunkhash].css', ignoreOrder: false, // Enable to remove warnings about conflicting order }), new WebpackManifestPlugin({ fileName: path.resolve('config/webpack.yml'), publicPath: `wp-${webpackVersion}`, generate: (seed, files, entrypoints) => Object.keys(entrypoints).reduce((acc, fs) => ({...acc, [fs]: files.filter(file => entrypoints[fs].filter(basename => !(/\.map$/.test(basename))).some(basename => file.path.endsWith(basename))).filter(file => file.isInitial).map(file => file.path)}), {}), serialize: yaml.dump }), new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: [ path.resolve(staticDir), path.resolve(wellKnownDir), ] }), new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }), new webpack.DefinePlugin({ VERSION: JSON.stringify(packageVersion) }), ...(() => { const langs = new Set(); function findLangs(json, langs = new Set()) { if (json && json._i18n) { Object.keys(json).forEach(key => { if (key !== '_i18n') { langs.add(key); } }) } else { console.error('Invalid i18nJson format!'); } return langs; } findLangs(i18nJson, langs); const cacheHash = crypto.createHash('sha256'); cacheHash.update(JSON.stringify(langs)); const cacheFiles = new Set([ path.resolve('config/robots.txt'), path.resolve('assets/favicon.svg'), ]); for (const cacheFile of cacheFiles) { cacheHash.update(fs.readFileSync(cacheFile)); } const cacheDigest = cacheHash.copy().digest('hex'); let cachedVersion = undefined; const versionFile = path.resolve(wellKnownCacheDir, `${cacheDigest}.version`); try { if (fs.existsSync(versionFile)) { cachedVersion = fs.readFileSync(versionFile, 'utf8'); } } catch (e) { console.error(e); } const versionDigest = cacheHash.digest('hex'); return Array.from(langs).map(lang => { return [ new CopyPlugin({ patterns: [ { from: 'config/robots.txt', to: path.resolve(wellKnownDir, lang, 'robots.txt') }, { from: `${faviconsDir}/include.html`, to: path.resolve(wellKnownDir, lang, 'html_code.html') }, { from: `${faviconsDir}/*.png`, to: path.resolve(wellKnownDir, lang, '[name][ext]') }, ] }), ]; }).flat(1); })() ], output: { chunkFilename: '[chunkhash].js', filename: '[chunkhash].js', path: path.resolve(staticDir, `wp-${webpackVersion}`), publicPath: `/${staticDir}/res/wp-${webpackVersion}/`, hashFunction: 'shake256', hashDigestLength: 36 }, optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { sourceMap: true, }, }), new CssMinimizerPlugin({ parallel: true, minimizerOptions: { preset: [ 'default', { discardComments: { removeAll: true, } }, ], }, }), ], moduleIds: 'named', chunkIds: 'named', runtimeChunk: 'single', realContentHash: false }, mode: 'production', recordsPath: path.join(path.resolve('records.json')), performance: { assetFilter: (assetFilename) => !(/\.(map|svg|ttf|eot)$/.test(assetFilename)) }, devtool: 'source-map' }; }; module.exports = webpackConfig;