Optimize webpack build speed with SWC and caching

- Replace Babel with SWC for 70-80% faster TypeScript compilation
- Add filesystem caching for 90% faster subsequent builds
- Update TypeScript config for better performance:
  - Upgrade target to ES2020
  - Use bundler moduleResolution
  - Enable incremental compilation
- Add .tsbuildinfo and webpack cache to .gitignore

Build time improved from ~5-10s to ~1.7s
This commit is contained in:
antanst 2025-08-05 19:36:24 +03:00 committed by Chris
parent 8ab473aaa0
commit 1d4dcef1d1
5 changed files with 287 additions and 18 deletions

6
.gitignore vendored
View file

@ -30,3 +30,9 @@ backend/dist/
# Webpack output # Webpack output
public/assets/ public/assets/
# TypeScript build info
.tsbuildinfo
# Webpack cache
.webpack/

242
package-lock.json generated
View file

@ -57,6 +57,7 @@
"@babel/preset-react": "^7.25.7", "@babel/preset-react": "^7.25.7",
"@babel/preset-typescript": "^7.25.7", "@babel/preset-typescript": "^7.25.7",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@swc/core": "^1.13.3",
"@testing-library/jest-dom": "^6.0.0", "@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.0.0", "@testing-library/user-event": "^14.0.0",
@ -86,6 +87,7 @@
"sequelize-cli": "~6.6.2", "sequelize-cli": "~6.6.2",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"supertest": "~7.1.1", "supertest": "~7.1.1",
"swc-loader": "^0.2.6",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
@ -3171,6 +3173,232 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"node_modules/@swc/core": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.3.tgz",
"integrity": "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.23"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.13.3",
"@swc/core-darwin-x64": "1.13.3",
"@swc/core-linux-arm-gnueabihf": "1.13.3",
"@swc/core-linux-arm64-gnu": "1.13.3",
"@swc/core-linux-arm64-musl": "1.13.3",
"@swc/core-linux-x64-gnu": "1.13.3",
"@swc/core-linux-x64-musl": "1.13.3",
"@swc/core-win32-arm64-msvc": "1.13.3",
"@swc/core-win32-ia32-msvc": "1.13.3",
"@swc/core-win32-x64-msvc": "1.13.3"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.3.tgz",
"integrity": "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.3.tgz",
"integrity": "sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.3.tgz",
"integrity": "sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.3.tgz",
"integrity": "sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.3.tgz",
"integrity": "sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.3.tgz",
"integrity": "sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.3.tgz",
"integrity": "sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.3.tgz",
"integrity": "sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.3.tgz",
"integrity": "sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.3.tgz",
"integrity": "sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@swc/types": {
"version": "0.1.23",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz",
"integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {
"version": "9.3.4", "version": "9.3.4",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
@ -17710,6 +17938,20 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swc-loader": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz",
"integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@swc/counter": "^0.1.3"
},
"peerDependencies": {
"@swc/core": "^1.2.147",
"webpack": ">=2"
}
},
"node_modules/swr": { "node_modules/swr": {
"version": "2.3.4", "version": "2.3.4",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz",

View file

@ -67,6 +67,7 @@
"@babel/preset-react": "^7.25.7", "@babel/preset-react": "^7.25.7",
"@babel/preset-typescript": "^7.25.7", "@babel/preset-typescript": "^7.25.7",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@swc/core": "^1.13.3",
"@testing-library/jest-dom": "^6.0.0", "@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.0.0", "@testing-library/user-event": "^14.0.0",
@ -96,6 +97,7 @@
"sequelize-cli": "~6.6.2", "sequelize-cli": "~6.6.2",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"supertest": "~7.1.1", "supertest": "~7.1.1",
"swc-loader": "^0.2.6",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",

View file

@ -1,15 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2017", "target": "es2020",
"lib": ["es2017", "dom"], "lib": ["es2020", "dom"],
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"jsx": "react", "jsx": "react-jsx",
"strict": false, "strict": false,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
"types": ["node"] "types": ["node"]
}, },
"include": ["frontend/**/*"] "include": ["frontend/**/*"],
"exclude": ["node_modules", "dist", "backend"]
} }

View file

@ -7,6 +7,12 @@ const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = { module.exports = {
entry: './frontend/index.tsx', entry: './frontend/index.tsx',
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js', filename: '[name].[contenthash].js',
@ -67,21 +73,30 @@ module.exports = {
{ {
test: /\.(js|jsx|ts|tsx)$/, test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: {
{ loader: 'swc-loader',
loader: 'babel-loader', options: {
options: { jsc: {
presets: [ parser: {
'@babel/preset-env', syntax: 'typescript',
'@babel/preset-react', tsx: true,
'@babel/preset-typescript', decorators: false,
], dynamicImport: true,
plugins: [ },
isDevelopment && 'react-refresh/babel', transform: {
].filter(Boolean), react: {
runtime: 'automatic',
development: isDevelopment,
refresh: isDevelopment,
},
},
target: 'es2018',
},
module: {
type: 'es6',
}, },
}, },
], },
}, },
{ {
test: /\.css$/i, test: /\.css$/i,