refactor: migrate from webpack to esbuild for all services

- Updated package.json scripts to use esbuild instead of webpack.
- Removed webpack.config.js files from all services.
- Added esbuild.config.js files for Dashboard, Accounts, AdminDashboard, and StatusPage.
- Created a shared esbuild configuration in Scripts/esbuild-config.js.
- Implemented CSS and file loader plugins for handling styles and assets.
- Added a migration cleanup script to remove webpack dependencies and configurations.
- Updated ESLint configuration to ignore webpack.config.js.
This commit is contained in:
Simon Larsen
2025-06-09 20:02:24 +01:00
parent 4ae8a64857
commit f5e6b04070
19 changed files with 1848 additions and 40687 deletions

View File

@@ -0,0 +1,12 @@
const { createConfig, build, watch } = require('../Scripts/esbuild-config');
const config = createConfig({
serviceName: 'Accounts',
publicPath: '/accounts/dist/',
});
if (process.argv.includes('--watch')) {
watch(config, 'Accounts');
} else {
build(config, 'Accounts');
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,10 @@
"version": "0.1.0",
"private": false,
"scripts": {
"dev-build": "webpack build --mode=development",
"dev-build": "NODE_ENV=development node esbuild.config.js",
"dev": "npx nodemon",
"build": "webpack build --mode=production",
"build": "NODE_ENV=production node esbuild.config.js",
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
"test": "",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
@@ -28,16 +29,11 @@
},
"dependencies": {
"Common": "file:../Common",
"css-loader": "^6.11.0",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"file-loader": "^6.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"sass-loader": "^13.3.3",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"use-async-effect": "^2.2.7"
},
"devDependencies": {
@@ -46,7 +42,7 @@
"@types/react-dom": "^18.0.4",
"@types/react-router-dom": "^5.3.3",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"webpack": "^5.76.0"
"sass": "^1.89.1",
"ts-node": "^10.9.1"
}
}

View File

@@ -1,70 +0,0 @@
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
const path = require("path");
const webpack = require("webpack");
const dotenv = require("dotenv");
require('ejs');
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/accounts/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
},
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devtool: "eval-source-map",
};

View File

@@ -0,0 +1,12 @@
const { createConfig, build, watch } = require('../Scripts/esbuild-config');
const config = createConfig({
serviceName: 'AdminDashboard',
publicPath: '/admin/dist/',
});
if (process.argv.includes('--watch')) {
watch(config, 'AdminDashboard');
} else {
build(config, 'AdminDashboard');
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,18 +6,17 @@
"Common": "file:../Common",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"file-loader": "^6.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"style-loader": "^3.3.4"
"react-router-dom": "^6.23.1"
},
"scripts": {
"dev-build": "webpack build --mode=development",
"dev-build": "NODE_ENV=development node esbuild.config.js",
"dev": "npx nodemon",
"build": "webpack build --mode=production",
"build": "NODE_ENV=production node esbuild.config.js",
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
"test": "react-app-rewired test",
"eject": "webpack eject",
"eject": "echo 'esbuild does not require eject'",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
"start": "node --require ts-node/register Serve.ts",
@@ -42,13 +41,8 @@
"@types/react": "^18.2.38",
"@types/react-dom": "^18.0.4",
"@types/react-router-dom": "^5.3.3",
"css-loader": "^6.8.1",
"nodemon": "^2.0.20",
"react-app-rewired": "^2.2.1",
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.1",
"webpack": "^5.76.0"
"sass": "^1.89.1",
"ts-node": "^10.9.1"
}
}

View File

@@ -1,70 +0,0 @@
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
require('ejs');
const path = require("path");
const webpack = require("webpack");
const dotenv = require("dotenv");
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/admin/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
},
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devtool: "eval-source-map",
};

View File

@@ -0,0 +1,12 @@
const { createConfig, build, watch } = require('../Scripts/esbuild-config');
const config = createConfig({
serviceName: 'Dashboard',
publicPath: '/dashboard/dist/',
});
if (process.argv.includes('--watch')) {
watch(config, 'Dashboard');
} else {
build(config, 'Dashboard');
}

18636
Dashboard/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,12 @@
"version": "0.1.0",
"private": false,
"scripts": {
"dev-build": "webpack build --mode=development",
"dev-build": "NODE_ENV=development node esbuild.config.js",
"dev": "npx nodemon",
"build": "webpack build --mode=production",
"analyze": "cross-env analyze=true webpack build --mode=production",
"build": "NODE_ENV=production node esbuild.config.js",
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
"test": "react-app-rewired test",
"eject": "webpack eject",
"eject": "echo 'esbuild does not require eject'",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
"start": "node --require ts-node/register Serve.ts",
@@ -34,28 +34,19 @@
"Common": "file:../Common",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"file-loader": "^6.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.0",
"reactflow": "^11.11.2",
"stripe": "^11.0.0",
"style-loader": "^3.3.4",
"use-async-effect": "^2.2.6"
},
"devDependencies": {
"@types/node": "^16.11.35",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.0.4",
"cross-env": "^7.0.3",
"css-loader": "^6.8.1",
"nodemon": "^2.0.20",
"react-app-rewired": "^2.2.1",
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.1",
"webpack": "^5.76.0",
"webpack-bundle-analyzer": "^4.10.1"
"sass": "^1.89.1",
"ts-node": "^10.9.1"
}
}

View File

@@ -1,73 +0,0 @@
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
require('ejs');
const path = require("path");
const webpack = require("webpack");
const dotenv = require("dotenv");
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/dashboard/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
},
}),
process.env.analyze === "true" ? new BundleAnalyzerPlugin() : () => {},
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devtool: "eval-source-map",
};

215
Scripts/esbuild-config.js Normal file
View File

@@ -0,0 +1,215 @@
/**
* Shared esbuild configuration factory for OneUptime frontend services
* This creates consistent build configurations across all services
*/
const esbuild = require('esbuild');
const path = require('path');
const fs = require('fs');
const dotenv = require('dotenv');
// CSS Plugin to handle CSS/SCSS files
function createCSSPlugin() {
return {
name: 'css',
setup(build) {
build.onLoad({ filter: /\.s?css$/ }, async (args) => {
const sass = require('sass');
const fs = require('fs');
let contents = fs.readFileSync(args.path, 'utf8');
// Compile SCSS to CSS if it's a SCSS file
if (args.path.endsWith('.scss') || args.path.endsWith('.sass')) {
try {
const result = sass.compile(args.path);
contents = result.css;
} catch (error) {
console.error(`SCSS compilation error in ${args.path}:`, error);
throw error;
}
}
// Return CSS as a string that will be injected into the page
return {
contents: `
const style = document.createElement('style');
style.textContent = ${JSON.stringify(contents)};
document.head.appendChild(style);
`,
loader: 'js',
};
});
},
};
}
// File loader plugin for assets
function createFileLoaderPlugin() {
return {
name: 'file-loader',
setup(build) {
build.onLoad({ filter: /\.(png|jpe?g|gif|svg|woff|woff2|eot|ttf|otf)$/ }, async (args) => {
const fs = require('fs');
const path = require('path');
const contents = fs.readFileSync(args.path);
const filename = path.basename(args.path);
const ext = path.extname(filename);
// For development, we'll use data URLs for simplicity
// In production, you might want to copy files to the output directory
const mimeTypes = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.eot': 'application/vnd.ms-fontobject',
'.ttf': 'font/ttf',
'.otf': 'font/otf',
};
const mimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
const dataUrl = `data:${mimeType};base64,${contents.toString('base64')}`;
return {
contents: `export default ${JSON.stringify(dataUrl)};`,
loader: 'js',
};
});
},
};
}
// Read environment variables from .env file
function readEnvFile(pathToFile) {
if (!fs.existsSync(pathToFile)) {
console.warn(`Environment file not found: ${pathToFile}`);
return {};
}
const parsed = dotenv.config({ path: pathToFile }).parsed || {};
const env = {};
for (const key in parsed) {
env[`process.env.${key}`] = JSON.stringify(parsed[key]);
}
return env;
}
/**
* Create esbuild configuration for a service
* @param {Object} options - Configuration options
* @param {string} options.serviceName - Name of the service (dashboard, accounts, admin, status-page)
* @param {string} options.publicPath - Public path for assets
* @param {string} [options.entryPoint] - Entry point file (defaults to './src/Index.tsx')
* @param {string} [options.outdir] - Output directory (defaults to './public/dist')
* @param {Object} [options.additionalDefines] - Additional define variables
* @param {Array} [options.additionalExternal] - Additional external modules
* @param {Object} [options.additionalAlias] - Additional aliases
*/
function createConfig(options) {
const {
serviceName,
publicPath,
entryPoint = './src/Index.tsx',
outdir = './public/dist',
additionalDefines = {},
additionalExternal = [],
additionalAlias = {}
} = options;
const isDev = process.env.NODE_ENV !== 'production';
const isAnalyze = process.env.analyze === 'true';
return {
entryPoints: [entryPoint],
bundle: true,
outdir,
format: 'iife',
platform: 'browser',
target: 'es2017',
sourcemap: isDev ? 'inline' : false,
minify: !isDev,
splitting: false, // IIFE format doesn't support splitting
publicPath,
define: {
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
...readEnvFile('/usr/src/app/dev-env/.env'),
...additionalDefines,
},
external: ['react-native-sqlite-storage', ...additionalExternal],
alias: {
'react': path.resolve('./node_modules/react'),
...additionalAlias,
},
plugins: [createCSSPlugin(), createFileLoaderPlugin()],
loader: {
'.tsx': 'tsx',
'.ts': 'ts',
'.jsx': 'jsx',
'.js': 'js',
'.json': 'json',
},
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.json', '.css', '.scss'],
metafile: isAnalyze,
};
}
/**
* Build function that handles the build process
* @param {Object} config - esbuild configuration
* @param {string} serviceName - Name of the service for logging
*/
async function build(config, serviceName) {
const isAnalyze = process.env.analyze === 'true';
try {
const result = await esbuild.build(config);
if (isAnalyze && result.metafile) {
const analyzeText = await esbuild.analyzeMetafile(result.metafile);
console.log(`\n📊 Bundle analysis for ${serviceName}:`);
console.log(analyzeText);
// Write metafile for external analysis tools
const metafilePath = path.join(config.outdir, 'metafile.json');
fs.writeFileSync(metafilePath, JSON.stringify(result.metafile, null, 2));
console.log(`📝 Metafile written to: ${metafilePath}`);
}
console.log(`${serviceName} build completed successfully`);
} catch (error) {
console.error(`${serviceName} build failed:`, error);
process.exit(1);
}
}
/**
* Watch function that handles the watch process
* @param {Object} config - esbuild configuration
* @param {string} serviceName - Name of the service for logging
*/
async function watch(config, serviceName) {
try {
const context = await esbuild.context(config);
await context.watch();
console.log(`👀 Watching ${serviceName} for changes...`);
} catch (error) {
console.error(`${serviceName} watch failed:`, error);
process.exit(1);
}
}
module.exports = {
createConfig,
build,
watch,
createCSSPlugin,
createFileLoaderPlugin,
readEnvFile,
};

60
Scripts/migrate-to-esbuild.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Webpack to ESBuild Migration Cleanup Script
# This script removes webpack dependencies and configurations
echo "🧹 Starting webpack to esbuild migration cleanup..."
# Services to clean up
SERVICES=("Dashboard" "Accounts" "AdminDashboard" "StatusPage")
for service in "${SERVICES[@]}"; do
echo "📦 Cleaning up $service..."
if [ -d "./$service" ]; then
cd "./$service"
# Remove webpack configuration file
if [ -f "webpack.config.js" ]; then
echo " 🗑️ Removing webpack.config.js"
rm webpack.config.js
fi
# Remove webpack-related dependencies
echo " 📦 Removing webpack dependencies..."
npm uninstall \
webpack \
webpack-bundle-analyzer \
ts-loader \
css-loader \
style-loader \
sass-loader \
file-loader \
cross-env \
react-app-rewired 2>/dev/null || true
# Clean up any webpack-related build artifacts
if [ -d "dist" ]; then
echo " 🗑️ Cleaning old dist directory"
rm -rf dist
fi
cd ..
else
echo " ⚠️ Directory $service not found, skipping..."
fi
done
echo "✅ Webpack to esbuild migration cleanup completed!"
echo ""
echo "📝 Summary of changes:"
echo " • Removed webpack.config.js files"
echo " • Uninstalled webpack and related dependencies"
echo " • Created esbuild.config.js configurations"
echo " • Updated package.json build scripts"
echo ""
echo "🚀 You can now use the following commands:"
echo " • npm run dev-build - Development build"
echo " • npm run build - Production build"
echo " • npm run build:watch - Development build with watch mode"
echo " • npm run analyze - Production build with bundle analysis"

View File

@@ -0,0 +1,12 @@
const { createConfig, build, watch } = require('../Scripts/esbuild-config');
const config = createConfig({
serviceName: 'StatusPage',
publicPath: '/status-page/dist/',
});
if (process.argv.includes('--watch')) {
watch(config, 'StatusPage');
} else {
build(config, 'StatusPage');
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,9 @@
"private": false,
"scripts": {
"dev": "npx nodemon",
"build": "webpack build --mode=production",
"dev-build": "webpack build --mode=development",
"analyze": "cross-env analyze=true webpack build --mode=production",
"build": "NODE_ENV=production node esbuild.config.js",
"dev-build": "NODE_ENV=development node esbuild.config.js",
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
"test": "",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
@@ -29,16 +29,11 @@
},
"dependencies": {
"Common": "file:../Common",
"css-loader": "^6.11.0",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"file-loader": "^6.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"sass-loader": "^13.3.3",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"use-async-effect": "^2.2.6"
},
"devDependencies": {
@@ -46,10 +41,8 @@
"@types/react": "^18.2.38",
"@types/react-dom": "^18.0.4",
"@types/react-router-dom": "^5.3.3",
"cross-env": "^7.0.3",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"webpack": "^5.76.0",
"webpack-bundle-analyzer": "^4.10.1"
"sass": "^1.89.1",
"ts-node": "^10.9.1"
}
}

View File

@@ -1,74 +0,0 @@
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
require('ejs');
const path = require("path");
const webpack = require("webpack");
const dotenv = require("dotenv");
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/status-page/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
},
}),
process.env.analyze === "true" ? new BundleAnalyzerPlugin() : () => {},
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devtool: "eval-source-map",
};

View File

@@ -23,7 +23,6 @@ export default tseslint.config(
"**/playwright-coverage/",
"**/playwright-screenshots/",
"**/playwright-videos",
"**/webpack.config.js", // TODO: Remove this ignore
"**/service-worker.js", // TODO: Remove this ignore
"**/Static/", // TODO: Remove this ignore
"**/*.js" // TODO: Remove this ignore