Files
LiveSplitOne/webpack.config.js
T
Christopher Serr 349d95ebb0 Context Menu, CSS Modules and React 19 (#1015)
This replaces the context menu with our own. By doing that we no longer
need to force install our npm packages, as there are no longer any
conflicts. In fact, as part of this change, I updated us to the latest
React 19 and even enabled the React compiler.

I also explored CSS modules for the first time, which are going to allow
us to reduce conflicts between CSS rules and possibly even allow us to
fully remove SASS, as the need for it is going to be reduced.

I also found out that the latest Rust nightly is broken due to their
update to LLVM 20. So I pinned the version to the last working nightly
version.
2025-02-20 21:40:55 +00:00

234 lines
8.8 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,
},
},
},
"sass-loader",
],
},
{
test: /\.(png|jpg|gif|woff|ico|svg)$/,
type: 'asset/resource'
},
],
},
mode: isProduction ? "production" : "development",
};
};