diff --git a/.babelrc.json b/.babelrc.json
deleted file mode 100644
index dfe57f2..0000000
--- a/.babelrc.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "presets": [
- ["@babel/env", {
- "targets": {
- "chrome": "57",
- "firefox": "52",
- "safari": "10.3",
- "edge": "16",
- "opera": "44"
- }
- }],
- "@babel/preset-react"
- ]
-}
diff --git a/.eslintrc.json b/.eslintrc.json
index f2b0ea2..7818ff7 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,7 +5,6 @@
"es6": true
},
"extends": ["eslint:recommended", "standard", "standard-jsx", "react-app"],
- "parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": "7",
"ecmaFeatures": {
diff --git a/Makefile b/Makefile
index e590c8f..abaa718 100644
--- a/Makefile
+++ b/Makefile
@@ -81,8 +81,8 @@ $(SPEC): packaging/$(SPEC).in $(NODE_MODULES_TEST)
provides=$$(npm ls --omit dev --package-lock-only --depth=Infinity | grep -Eo '[^[:space:]]+@[^[:space:]]+' | sort -u | sed 's/^/Provides: bundled(npm(/; s/\(.*\)@/\1)) = /'); \
awk -v p="$$provides" '{gsub(/%{VERSION}/, "$(VERSION)"); gsub(/%{NPM_PROVIDES}/, p)}1' $< > $@
-$(DIST_TEST): $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP) $(shell find src/ -type f) package.json webpack.config.js
- NODE_ENV=$(NODE_ENV) node_modules/.bin/webpack
+$(DIST_TEST): $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP) $(shell find src/ -type f) package.json build.js
+ NODE_ENV=$(NODE_ENV) ./build.js
watch:
NODE_ENV=$(NODE_ENV) npm run watch
diff --git a/README.md b/README.md
index 5ea7479..0ac7bc4 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ After changing the code and running `make` again, reload the Cockpit page in
your browser.
You can also use
-[watch mode](https://webpack.js.org/guides/development/#using-watch-mode) to
+[watch mode](https://esbuild.github.io/api/#watch) to
automatically update the bundle on every code change with
$ npm run watch
@@ -106,11 +106,11 @@ Violations of some rules can be fixed automatically by:
Rules configuration can be found in the `.stylelintrc.json` file.
-During fast iterative development, you can also choose to not run stylelint.
+During fast iterative development, you can also choose to not run eslint/stylelint.
This speeds up the build and avoids build failures due to e. g. ill-formatted
css or other issues:
- $ make STYLELINT=0
+ $ make LINT=0
# Running tests locally
diff --git a/build.js b/build.js
new file mode 100755
index 0000000..6b62d01
--- /dev/null
+++ b/build.js
@@ -0,0 +1,136 @@
+#!/usr/bin/env node
+
+import fs from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+
+import copy from 'esbuild-plugin-copy';
+
+import { cleanPlugin } from './pkg/lib/esbuild-cleanup-plugin.js';
+import { cockpitCompressPlugin } from './pkg/lib/esbuild-compress-plugin.js';
+import { cockpitPoEsbuildPlugin } from './pkg/lib/cockpit-po-plugin.js';
+import { cockpitRsyncEsbuildPlugin } from './pkg/lib/cockpit-rsync-plugin.js';
+import { esbuildStylesPlugins } from './pkg/lib/esbuild-common.js';
+import { eslintPlugin } from './pkg/lib/esbuild-eslint-plugin.js';
+import { stylelintPlugin } from './pkg/lib/esbuild-stylelint-plugin.js';
+
+const useWasm = os.arch() !== 'x64';
+const esbuild = (await import(useWasm ? 'esbuild-wasm' : 'esbuild')).default;
+
+const production = process.env.NODE_ENV === 'production';
+const watchMode = process.env.ESBUILD_WATCH === "true";
+// linters dominate the build time, so disable them for production builds by default, but enable in watch mode
+const lint = process.env.LINT ? (process.env.LINT !== 0) : (watchMode || !production);
+// List of directories to use when using import statements
+const nodePaths = ['pkg/lib'];
+const outdir = 'dist';
+
+// Obtain package name from package.json
+const packageJson = JSON.parse(fs.readFileSync('package.json'));
+
+function notifyEndPlugin() {
+ return {
+ name: 'notify-end',
+ setup(build) {
+ let startTime;
+
+ build.onStart(() => {
+ startTime = new Date();
+ });
+
+ build.onEnd(() => {
+ const endTime = new Date();
+ const timeStamp = endTime.toTimeString().split(' ')[0];
+ console.log(`${timeStamp}: Build finished in ${endTime - startTime} ms`);
+ });
+ }
+ };
+}
+
+const cwd = process.cwd();
+
+// similar to fs.watch(), but recursively watches all subdirectories
+function watch_dirs(dir, on_change) {
+ const callback = (ev, dir, fname) => {
+ // only listen for "change" events, as renames are noisy
+ // ignore hidden files
+ const isHidden = /^\./.test(fname);
+ if (ev !== "change" || isHidden) {
+ return;
+ }
+ on_change(path.join(dir, fname));
+ };
+
+ fs.watch(dir, {}, (ev, path) => callback(ev, dir, path));
+
+ // watch all subdirectories in dir
+ const d = fs.opendirSync(dir);
+ let dirent;
+
+ while ((dirent = d.readSync()) !== null) {
+ if (dirent.isDirectory())
+ watch_dirs(path.join(dir, dirent.name), on_change);
+ }
+ d.closeSync();
+}
+
+const context = await esbuild.context({
+ ...!production ? { sourcemap: "linked" } : {},
+ bundle: true,
+ entryPoints: ['./src/index.js'],
+ external: ['*.woff', '*.woff2', '*.jpg', '*.svg', '../../assets*'], // Allow external font files which live in ../../static/fonts
+ legalComments: 'external', // Move all legal comments to a .LEGAL.txt file
+ loader: { ".js": "jsx" },
+ minify: production,
+ nodePaths,
+ outdir,
+ target: ['es2020'],
+ plugins: [
+ cleanPlugin(),
+ ...lint
+ ? [
+ stylelintPlugin({ filter: new RegExp(cwd + '\/src\/.*\.(css?|scss?)$') }),
+ eslintPlugin({ filter: new RegExp(cwd + '\/src\/.*\.(jsx?|js?)$') })
+ ]
+ : [],
+ // Esbuild will only copy assets that are explicitly imported and used
+ // in the code. This is a problem for index.html and manifest.json which are not imported
+ copy({
+ assets: [
+ { from: ['./src/manifest.json'], to: ['./manifest.json'] },
+ { from: ['./src/index.html'], to: ['./index.html'] },
+ ]
+ }),
+ ...esbuildStylesPlugins,
+ cockpitPoEsbuildPlugin(),
+ ...production ? [cockpitCompressPlugin()] : [],
+ cockpitRsyncEsbuildPlugin({ dest: packageJson.name }),
+ notifyEndPlugin(),
+ ]
+});
+
+try {
+ await context.rebuild();
+} catch (e) {
+ if (!watchMode)
+ process.exit(1);
+ // ignore errors in watch mode
+}
+
+if (watchMode) {
+ const on_change = async path => {
+ console.log("change detected:", path);
+ await context.cancel();
+
+ try {
+ await context.rebuild();
+ } catch (e) {} // ignore in watch mode
+ };
+
+ watch_dirs('src', on_change);
+
+ // wait forever until Control-C
+ await new Promise(() => {});
+}
+
+context.dispose();
diff --git a/package.json b/package.json
index 0bbcd20..f3d5179 100644
--- a/package.json
+++ b/package.json
@@ -7,25 +7,21 @@
"author": "",
"license": "LGPL-2.1",
"scripts": {
- "watch": "webpack --watch --progress",
- "build": "webpack",
+ "watch": "ESBUILD_WATCH='true' ./build.js",
+ "build": "./build.js",
"eslint": "eslint --ext .js --ext .jsx src/",
"eslint:fix": "eslint --fix --ext .js --ext .jsx src/",
"stylelint": "stylelint src/*{.css,scss}",
"stylelint:fix": "stylelint --fix src/*{.css,scss}"
},
"devDependencies": {
- "@babel/core": "^7.5.4",
- "@babel/eslint-parser": "^7.17.0",
- "@babel/preset-env": "^7.5.4",
- "@babel/preset-react": "^7.0.0",
"argparse": "^2.0.1",
- "babel-loader": "^8.0.6",
"chrome-remote-interface": "^0.32.1",
- "compression-webpack-plugin": "^9.0.0",
- "copy-webpack-plugin": "^9.0.0",
- "css-loader": "^6.5.0",
- "css-minimizer-webpack-plugin": "^3.0.1",
+ "esbuild": "^0.17.15",
+ "esbuild-plugin-copy": "^2.1.1",
+ "esbuild-plugin-replace": "^1.3.0",
+ "esbuild-sass-plugin": "^2.8.0",
+ "esbuild-wasm": "^0.17.16",
"eslint": "^8.13.0",
"eslint-config-react-app": "^7.0.0",
"eslint-config-standard": "^17.0.0-1",
@@ -36,22 +32,15 @@
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
- "eslint-webpack-plugin": "^3.1.1",
"htmlparser": "^1.7.7",
"jed": "^1.1.1",
- "mini-css-extract-plugin": "^2.5.3",
"po2json": "^1.0.0-alpha",
"qunit": "^2.9.3",
- "sass": "^1.35.2",
- "sass-loader": "^12.1.0",
+ "sass": "^1.61.0",
"sizzle": "^2.3.3",
- "string-replace-loader": "^3.0.0",
"stylelint": "^14.9.1",
"stylelint-config-standard-scss": "^5.0.0",
- "stylelint-webpack-plugin": "^3.3.0",
- "terser-webpack-plugin": "^5.1.4",
- "webpack": "^5.54.0",
- "webpack-cli": "^4.9.1"
+ "stylelint-formatter-pretty": "^3.2.0"
},
"dependencies": {
"@patternfly/patternfly": "4.224.4",
diff --git a/packaging/cockpit-starter-kit.spec.in b/packaging/cockpit-starter-kit.spec.in
index fc6141f..44d6952 100644
--- a/packaging/cockpit-starter-kit.spec.in
+++ b/packaging/cockpit-starter-kit.spec.in
@@ -48,7 +48,7 @@ appstream-util validate-relax --nonet %{buildroot}/%{_datadir}/metainfo/*
%files
%doc README.md
-%license LICENSE dist/index.js.LICENSE.txt.gz
+%license LICENSE dist/index.js.LEGAL.txt dist/index.css.LEGAL.txt
%{_datadir}/cockpit/*
%{_datadir}/metainfo/*
diff --git a/src/index.html b/src/index.html
index 3845f06..6eb0369 100644
--- a/src/index.html
+++ b/src/index.html
@@ -24,9 +24,8 @@ along with this package; If not, see