Skip to content

Commit da15634

Browse files
authored
chore: remove CRA and move to a pure esbuild build (#72)
react-scripts is deprecated and its dependencies are a source of vulnerabilties. This moves the build to a pure esbuild more in line with other projects.
1 parent 7e83059 commit da15634

File tree

9 files changed

+5576
-28566
lines changed

9 files changed

+5576
-28566
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ bower_components
3636

3737
# Compiled binary addons (https://nodejs.org/api/addons.html)
3838
build/Release
39+
ui/build
40+
ui/test-results
3941

4042
# Dependency directories
4143
node_modules/
@@ -101,3 +103,6 @@ dist
101103

102104
# TernJS port file
103105
.tern-port
106+
107+
# Downloaded binaries
108+
binaries

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ debug: ## Start the extension in debug mode
2121

2222
hot-reload: ## Enable hot reloading
2323
docker extension dev ui-source $(IMAGE) http://localhost:3000
24-
cd ui/ && npm start
24+
cd ui/ && npm run dev
2525

2626
stop-hot-realoading: ## Disable hot reloading
2727
docker extension dev reset $(IMAGE)

ui/package-lock.json

Lines changed: 5406 additions & 28543 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/package.json

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,37 @@
1313
"@mui/x-data-grid": "^5.17.12",
1414
"axios": "^0.30.2",
1515
"classnames": "^2.2.6",
16-
"cra-template": "1.1.3",
1716
"react": "^17.0.2",
1817
"react-dom": "^17.0.2",
19-
"react-scripts": "^5.0.0",
20-
"swr": "^1.3.0"
18+
"swr": "^1.3.0",
19+
"uuid": "^8.3.2"
2120
},
2221
"scripts": {
23-
"start": "react-scripts start",
24-
"build": "react-scripts build",
25-
"test": "react-scripts test",
26-
"eject": "react-scripts eject",
27-
"lint": "eslint --ext js,ts,tsx src"
28-
},
29-
"eslintConfig": {
30-
"extends": [
31-
"react-app",
32-
"react-app/jest"
33-
]
22+
"dev": "node ./scripts/esbuild.dev.mjs",
23+
"build": "node ./scripts/esbuild.build.mjs",
24+
"lint": "eslint --ext js,ts,tsx src",
25+
"typecheck": "tsc --noEmit"
3426
},
3527
"devDependencies": {
3628
"@babel/eslint-parser": "^7.19.1",
3729
"@docker/extension-api-client-types": "^0.3.0",
38-
"@types/jest": "^27.5.1",
30+
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
3931
"@types/node": "^17.0.35",
4032
"@types/react-dom": "^17.0.2",
4133
"@typescript-eslint/eslint-plugin": "^5.38.1",
34+
"@typescript-eslint/parser": "^5.38.1",
35+
"dotenv": "^11.0.0",
36+
"esbuild": "^0.19.12",
37+
"esbuild-plugin-clean": "^1.0.1",
38+
"esbuild-plugin-svgr": "^1.0.0",
4239
"eslint": "^8.24.0",
4340
"eslint-config-airbnb": "^19.0.4",
4441
"eslint-config-airbnb-base": "^15.0.0",
45-
"eslint-config-prettier": "^8.5.0",
46-
"eslint-config-standard-with-typescript": "^23.0.0",
47-
"eslint-plugin-import": "^2.23.4",
48-
"eslint-plugin-jsx-a11y": "^6.6.1",
49-
"eslint-plugin-n": "^15.2.5",
50-
"eslint-plugin-promise": "^6.0.1",
51-
"eslint-plugin-react": "^7.31.0",
42+
"eslint-config-prettier": "^8.10.0",
43+
"eslint-import-resolver-typescript": "^3.6.1",
44+
"eslint-plugin-import": "^2.27.5",
45+
"eslint-plugin-jsx-a11y": "^6.8.0",
46+
"eslint-plugin-react": "^7.34.1",
5247
"eslint-plugin-react-hooks": "^4.6.0",
5348
"typescript": "^4.6.4"
5449
}

ui/public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
</head>
88
<body>
99
<div id="root"></div>
10+
<script type="module" src="./assets/index.js"></script>
1011
</body>
1112
</html>

ui/scripts/esbuild.build.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { build } from 'esbuild';
2+
import { createConfig, copyStaticAssets } from './esbuild.common.mjs';
3+
4+
const run = async () => {
5+
const mode = 'production';
6+
try {
7+
await build(createConfig({ mode, clean: true }));
8+
await copyStaticAssets();
9+
console.log('esbuild: production bundle generated in ./build');
10+
} catch (error) {
11+
console.error(error);
12+
process.exitCode = 1;
13+
}
14+
};
15+
16+
run();

ui/scripts/esbuild.common.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import path from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
import { promises as fs } from 'node:fs';
4+
import dotenv from 'dotenv';
5+
import cleanPlugin from 'esbuild-plugin-clean';
6+
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
7+
import svgrPlugin from 'esbuild-plugin-svgr';
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
10+
export const rootDir = path.resolve(__dirname, '..');
11+
export const outDir = path.join(rootDir, 'build');
12+
export const publicDir = path.join(rootDir, 'public');
13+
const srcDir = path.join(rootDir, 'src');
14+
15+
const baseLoaders = {
16+
'.png': 'file',
17+
'.jpg': 'file',
18+
'.jpeg': 'file',
19+
'.gif': 'file',
20+
'.svg': 'file',
21+
};
22+
23+
dotenv.config({ path: path.join(rootDir, '.env') });
24+
25+
export const copyStaticAssets = async () => {
26+
try {
27+
await fs.access(publicDir);
28+
} catch (error) {
29+
if (error && error.code === 'ENOENT') {
30+
return;
31+
}
32+
throw error;
33+
}
34+
35+
await fs.mkdir(outDir, { recursive: true });
36+
await fs.cp(publicDir, outDir, { recursive: true });
37+
};
38+
39+
export const createConfig = ({ mode = 'development', clean = false } = {}) => {
40+
const isProd = mode === 'production';
41+
const plugins = [
42+
NodeModulesPolyfillPlugin(),
43+
svgrPlugin(),
44+
{
45+
name: 'copy-public-dir',
46+
setup(build) {
47+
build.onStart(async () => {
48+
await copyStaticAssets();
49+
});
50+
},
51+
},
52+
];
53+
54+
if (clean) {
55+
plugins.unshift(
56+
cleanPlugin({
57+
patterns: [path.join(outDir, '*')],
58+
}),
59+
);
60+
}
61+
62+
return {
63+
absWorkingDir: rootDir,
64+
entryPoints: [path.join(srcDir, 'index.tsx')],
65+
bundle: true,
66+
outdir: outDir,
67+
format: 'esm',
68+
sourcemap: isProd ? false : 'inline',
69+
minify: isProd,
70+
target: ['es2019'],
71+
tsconfig: path.join(rootDir, 'tsconfig.json'),
72+
jsx: 'automatic',
73+
loader: baseLoaders,
74+
entryNames: 'assets/[name]',
75+
chunkNames: 'assets/[name]',
76+
assetNames: 'assets/[name]',
77+
define: {
78+
'process.env.NODE_ENV': JSON.stringify(mode),
79+
},
80+
plugins,
81+
logLevel: 'info',
82+
};
83+
};

ui/scripts/esbuild.dev.mjs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { context } from 'esbuild';
2+
import { createConfig, outDir, copyStaticAssets, publicDir } from './esbuild.common.mjs';
3+
import { watch } from 'node:fs';
4+
5+
const port = Number(process.env.PORT ?? 3000);
6+
const host = process.env.HOST ?? '0.0.0.0';
7+
8+
const run = async () => {
9+
await copyStaticAssets();
10+
const ctx = await context(createConfig({ mode: 'development', clean: true }));
11+
await ctx.watch();
12+
const server = await ctx.serve({
13+
servedir: outDir,
14+
host,
15+
port,
16+
});
17+
18+
const urlHost = server.host === '0.0.0.0' ? 'localhost' : server.host;
19+
console.log(`esbuild dev server running at http://${urlHost}:${server.port}`);
20+
console.log('Press Ctrl+C to stop.');
21+
22+
try {
23+
watch(publicDir, async () => {
24+
try {
25+
await copyStaticAssets();
26+
} catch (error) {
27+
console.error('Failed to copy public assets', error);
28+
}
29+
});
30+
} catch (error) {
31+
console.warn('Static asset watcher unavailable:', error.message);
32+
}
33+
34+
const shutdown = async () => {
35+
await ctx.dispose();
36+
process.exit(0);
37+
};
38+
39+
process.on('SIGINT', shutdown);
40+
process.on('SIGTERM', shutdown);
41+
};
42+
43+
run().catch((error) => {
44+
console.error(error);
45+
process.exitCode = 1;
46+
});

ui/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"jsx": "react-jsx",
77
"module": "esnext",
88
"moduleResolution": "node",
9+
"types": ["node"],
910
"allowJs": true,
1011
"skipLibCheck": true,
1112
"esModuleInterop": true,

0 commit comments

Comments
 (0)