Files
LiveSplitOne/webpack.config.js
T
Christopher Serr f03689bc81 Convert Everything to CSS Modules (#1032)
This converts the remainder of the CSS files to CSS modules. The
following are not converted:
1. `variables.scss`: I'm not entirely sure if variables can be converted
   to CSS modules. The documentation states you might need PostCSS for
   it. However, we probably may want to look into CSS variables anyway.
   So this would be done in a subsequent PR.
2. `main.scss`: This declares global styles. I'm not sure if it's
   possible to convert this as this is needed from the very beginning,
   before any React is even loaded.
2025-03-31 18:42:11 +00:00

293 lines
11 KiB
JavaScript

import HtmlWebpackPlugin from "html-webpack-plugin";
import HtmlInlineScriptPlugin from "html-inline-script-webpack-plugin";
import FaviconsWebpackPlugin from "favicons-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import WorkboxPlugin from "workbox-webpack-plugin";
import ReactRefreshTypeScript from "react-refresh-typescript";
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import webpack from "webpack";
import { execSync } from "child_process";
import path from "path";
import { fileURLToPath } from "url";
import {
defineReactCompilerLoaderOption,
reactCompilerLoader,
} from "react-compiler-webpack";
function parseChangelog() {
return execSync('git log --grep "^Changelog: " -10')
.toString()
.split(/^commit /m)
.slice(1)
.map((commit) => {
const changelogIndex = commit.indexOf(" Changelog: ");
if (changelogIndex === -1) {
throw `Changelog not found in commit:\n${commit}`;
}
const dateString = commit.match(/^Date: (.*)$/m)?.[1];
if (!dateString) {
throw `Date not found in commit:\n${commit}`;
}
const dateValue = new Date(dateString);
const date = dateValue.toISOString().split("T")[0];
const id = commit.substring(0, commit.indexOf("\n"));
const message = commit
.substring(changelogIndex + 15)
.replaceAll("\n ", "\n")
.trim();
return {
id,
message,
date,
};
})
.filter((changelog) => changelog.message);
}
export default async (env, argv) => {
const getContributorsForRepo = async (repoName) => {
const contributorsData = await fetch(
`https://api.github.com/repos/LiveSplit/${repoName}/contributors`,
{
headers: {
Authorization: env.GITHUB_TOKEN
? `Bearer ${env.GITHUB_TOKEN}`
: undefined,
},
},
);
return contributorsData.json();
};
const lsoContributorsList = await getContributorsForRepo("LiveSplitOne");
const coreContributorsList = await getContributorsForRepo("livesplit-core");
const coreContributorsMap = {};
for (const coreContributor of coreContributorsList) {
if (
coreContributor.type === "User" &&
!coreContributor.login.includes("dependabot")
) {
coreContributorsMap[coreContributor.login] = coreContributor;
}
}
for (let lsoContributor of lsoContributorsList) {
const existingContributor = coreContributorsMap[lsoContributor.login];
if (existingContributor) {
existingContributor.contributions += lsoContributor.contributions;
} else if (
lsoContributor.type === "User" &&
!lsoContributor.login.includes("dependabot")
) {
coreContributorsMap[lsoContributor.login] = lsoContributor;
}
}
const contributorsList = Object.values(coreContributorsMap)
// Sort by contributions, but fallback to alphabetical order for the
// same amount of contributions
.sort((a, b) => (a.login > b.login ? 1 : b.login > a.login ? -1 : 0))
.sort((a, b) => b.contributions - a.contributions)
.map((user) => {
return { id: user.id, name: user.login };
});
const commitHash = execSync("git rev-parse --short HEAD").toString();
const date = new Date()
.toISOString()
.replace("T", " ")
.replace(/\..+/, " UTC");
const changelog = parseChangelog();
const basePath = path.dirname(fileURLToPath(import.meta.url));
const isProduction = argv.mode === "production";
const isTauri = env.TAURI === "true";
const distPath = path.join(
...[
basePath,
...(isTauri ? ["src-tauri", "target", "dist"] : ["dist"]),
],
);
return {
entry: {
bundle: ["./src/index.tsx"],
},
output: {
filename: "[name].js",
path: distPath,
publicPath: "",
},
devtool: isProduction ? undefined : "source-map",
devServer: {
port: 8080,
hot: true,
},
resolve: {
extensions: [
".webpack.js",
".web.js",
".ts",
".tsx",
".js",
".json",
".wasm",
],
},
plugins: [
...(isProduction
? [
new CleanWebpackPlugin({
protectWebpackAssets: false,
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"],
}),
]
: []),
...(isTauri
? []
: [
new FaviconsWebpackPlugin({
logo: path.resolve("src/assets/icon.svg"),
inject: true,
logoMaskable: path.resolve("src/assets/maskable.svg"),
favicons: {
appName: "LiveSplit One",
appDescription:
"A version of LiveSplit that works on a lot of platforms.",
developerName: "CryZe",
developerURL: "https://livesplit.org",
background: "#171717",
theme_color: "#232323",
appleStatusBarStyle: "black-translucent",
icons: {
appleIcon: {
offset: 10,
},
appleStartup: {
offset: 15,
},
windows: false,
coast: false,
yandex: false,
},
start_url: "/",
},
}),
]),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new webpack.DefinePlugin({
BUILD_DATE: JSON.stringify(date),
COMMIT_HASH: JSON.stringify(commitHash),
CONTRIBUTORS_LIST: JSON.stringify(contributorsList),
CHANGELOG: JSON.stringify(changelog),
}),
...(isProduction
? [
new HtmlInlineScriptPlugin({
scriptMatchPattern: ["^bundle.js$"],
}),
...(isTauri
? []
: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
maximumFileSizeToCacheInBytes:
100 * 1024 * 1024,
exclude: [/^assets/, /\.LICENSE\.txt$/],
runtimeCaching: [
{
urlPattern: (context) => {
return (
self.origin ===
context.url.origin &&
context.url.pathname.startsWith(
"/assets/",
)
);
},
handler: "CacheFirst",
},
],
}),
]),
]
: []),
...(!isProduction ? [new ReactRefreshWebpackPlugin()] : []),
],
module: {
rules: [
{
test: /\.tsx?$/,
use: [
...(isProduction
? [
{
loader: reactCompilerLoader,
options: defineReactCompilerLoaderOption({
babelTransFormOpt: {
plugins: [
"@babel/plugin-proposal-explicit-resource-management",
],
},
}),
},
]
: []),
{
loader: "ts-loader",
options: {
getCustomTransformers: () => ({
before: [
!isProduction &&
ReactRefreshTypeScript(),
].filter(Boolean),
}),
transpileOnly: !isProduction,
},
},
],
exclude: "/node_modules",
},
{
test: /\.(s?)css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: {
auto: true,
// In development mode, we want to know
// which classes are being used in the DOM.
localIdentName: isProduction
? "[hash:base64]"
: "[name]_[local]_[hash:base64]",
},
},
},
"sass-loader",
],
},
{
test: /\.(png|jpg|gif|woff|ico|svg)$/,
type: "asset/resource",
},
],
},
mode: isProduction ? "production" : "development",
};
};