import path from "path"; import { fileURLToPath } from "url"; import webpack from "webpack"; import WebpackAssetsManifest from "webpack-assets-manifest"; import CopyPlugin from "copy-webpack-plugin"; import MiniCssExtractPlugin from "mini-css-extract-plugin"; import CssMinimizerPlugin from "css-minimizer-webpack-plugin"; import TerserPlugin from "terser-webpack-plugin"; // PostCSS stuff: import autoprefixer from "autoprefixer"; import postcssSvgo from "postcss-svgo"; import postcssFocus from "postcss-focus"; import postcssColorRgbaFallback from "postcss-color-rgba-fallback"; import postcssMergeRules from "postcss-merge-rules"; import postcssDiscardDuplicates from "postcss-discard-duplicates"; import postcssCombineDuplicatedSelectors from "postcss-combine-duplicated-selectors"; import postcssNormalizeCharset from "postcss-normalize-charset"; // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#what-do-i-use-instead-of-__dirname-and-__filename const __dirname = path.dirname(fileURLToPath(import.meta.url)); const isProd = process.env.NODE_ENV === "production"; export default { entry: [ path.resolve(__dirname, "assets/js/index.js"), path.resolve(__dirname, "assets/sass/main.scss"), ], mode: isProd ? "production" : "development", devtool: isProd ? "source-map" : "inline-source-map", output: { filename: isProd ? "js/[name]-[contenthash:6].js" : "js/[name].js", path: path.resolve(__dirname, "static/assets/"), publicPath: "/assets/", clean: true, crossOriginLoading: "anonymous", environment: { // https://github.com/babel/babel-loader#top-level-function-iife-is-still-arrow-on-webpack-5 arrowFunction: false, }, }, plugins: [ new MiniCssExtractPlugin({ filename: isProd ? "css/[name]-[contenthash:6].css" : "css/[name].css", }), new webpack.BannerPlugin({ banner: `@license MIT @copyright (c) 2017-${new Date().getFullYear()} Jake Jarvis `, }), new CopyPlugin({ patterns: [ { from: path.resolve(__dirname, "assets/images/"), to: "images/", }, { from: path.resolve(__dirname, "node_modules/twemoji-emojis/vendor/svg/"), to: "emoji/", }, ], }), new WebpackAssetsManifest({ writeToDisk: true, // allow Hugo to access file in dev mode output: path.resolve(__dirname, "data/manifest.json"), publicPath: true, integrity: true, integrityHashes: ["sha384"], customize: (entry) => { // don't add thousands of unneeded twemoji graphics to the manifest if (entry.key.startsWith("emoji/")) return false; }, }), ], module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: "babel-loader", options: { presets: [ [ "@babel/preset-env", { include: [ "transform-arrow-functions", "transform-block-scoping", "transform-template-literals", ], bugfixes: true, useBuiltIns: "entry", corejs: 3, }, ], ], plugins: [ [ "template-html-minifier", { modules: { "lit-html": ["html"], "lit-element": ["html"], }, htmlMinifier: { html5: true, collapseWhitespace: true, conservativeCollapse: true, removeComments: false, caseSensitive: true, }, }, ], ], }, }, ], }, { test: /\.(sa|sc|c)ss$/, use: [ MiniCssExtractPlugin.loader, { loader: "css-loader", options: { sourceMap: true, }, }, { loader: "postcss-loader", options: { sourceMap: true, postcssOptions: { config: false, plugins: [ autoprefixer(), postcssSvgo({ encode: true, }), postcssFocus(), postcssColorRgbaFallback({ properties: [ "background-image", ], }), postcssCombineDuplicatedSelectors(), postcssMergeRules(), postcssDiscardDuplicates(), postcssNormalizeCharset(), ], }, }, }, { loader: "sass-loader", options: { sourceMap: true, }, }, ], }, { test: /\.(woff(2)?|ttf|otf|eot)$/, type: "asset/resource", generator: { filename: "fonts/[name][ext]", } }, ], }, performance: { // ignore performance hints on source maps and JSON asset manifest assetFilter: (assetFilename) => !/\.map|\.json$/.test(assetFilename), }, optimization: { sideEffects: true, minimize: isProd, minimizer: [ new TerserPlugin({ test: /\.js$/, parallel: true, terserOptions: { compress: { arrows: false, drop_console: true, negate_iife: false, sequences: false, passes: 3, }, format: { comments: /^\**!|@preserve|@license/i, ascii_only: true, // some symbols get disfigured otherwise }, mangle: true, }, extractComments: false, }), new CssMinimizerPlugin({ test: /\.css$/, minify: CssMinimizerPlugin.cleanCssMinify, minimizerOptions: { compatibility: "*", level: 1, processImport: false, format: { breaks: { afterAtRule: true, afterBlockBegins: true, afterBlockEnds: true, afterComment: true, afterRuleEnds: true, beforeBlockEnds: true, }, spaces: { beforeBlockBegins: true, }, semicolonAfterLastProperty: true, }, }, }), ], }, devServer: { static: { directory: path.resolve(__dirname, "public"), watch: true, }, host: "0.0.0.0", // weird docker bind behavior port: process.env.PORT ?? 1337, compress: true, liveReload: true, }, };