// SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , David Mosbach , Gregor Kleen , Sarah Vaupel , Sarah Vaupel // // SPDX-License-Identifier: AGPL-3.0-or-later import webpack from 'webpack'; import { resolve, join, relative } from 'node:path'; import { execSync } from 'node:child_process'; import tmp from 'tmp'; tmp.setGracefulCleanup(); import fs from 'fs-extra'; import { glob, globSync } from 'glob'; import axios from 'axios'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin'; import yaml from 'js-yaml'; import postcssPresetEnv from 'postcss-preset-env'; import RemovePlugin from 'remove-files-webpack-plugin'; import crypto from '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' }; import webpackJson from 'webpack/package.json' assert { type: "json" }; const webpackVersion = webpackJson.version.split('.').slice(0, 2).join('.'); import packageJson from './package.json' assert { type: "json" }; const packageVersion = packageJson.version; import i18nJson from './config/i18n.json' assert { type: '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: ['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: /\.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { sourceMap: true }}, { loader: 'postcss-loader', options: { sourceMap: true, postcssOptions: { plugins: [ 'postcss-preset-env' ] } }}, { loader: 'resolve-url-loader', options: { sourceMap: true }} ] }, { test: /\.s(c|a)ss$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { sourceMap: true }}, { loader: 'postcss-loader', options: { sourceMap: true, postcssOptions: { plugins: [ 'postcss-preset-env' ] } }}, { loader: 'resolve-url-loader', options: { sourceMap: true }}, { loader: 'sass-loader', options: { implementation: import('sass'), sourceMap: true }} ] }, { test: /\.(woff(2)?|ttf|eot|svg)(\?.*)?$/i, type: 'asset' } ] }, entry: { main: [ resolve('frontend/src/polyfill.js'), 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: 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: [ resolve(staticDir), 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([ resolve('config/robots.txt'), 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 = 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: resolve(wellKnownDir, lang, 'robots.txt') }, { from: `${faviconsDir}/include.html`, to: resolve(wellKnownDir, lang, 'html_code.html') }, { from: `${faviconsDir}/*.png`, to: resolve(wellKnownDir, lang, '[name][ext]') }, ] }), ]; }).flat(1); })() ], output: { chunkFilename: '[chunkhash].js', filename: '[chunkhash].js', 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 MiniCssExtractPlugin(), ], moduleIds: 'named', chunkIds: 'named', runtimeChunk: 'single', realContentHash: false }, mode: 'production', recordsPath: join(resolve('records.json')), performance: { assetFilter: (assetFilename) => !(/\.(map|svg|ttf|eot)$/.test(assetFilename)) }, devtool: 'source-map' }; } export default webpackConfig;