From d43945d93bb972149dfdfe793579a4f212b1154e Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 3 Feb 2023 18:43:21 +0100 Subject: [PATCH] maint: Drop webpack in favor of esbuild For eslint integration there are two existing plugins [1], [2] but none can be used because of unsatisfied peer dependency version of esbuild. Let's just use our own eslint plugin for now. [1] https://github.com/to-codando/esbuild-plugin-linter/issues/1 [2] https://github.com/robinloeffel/esbuild-plugin-eslint/issues/5 TODO: [ ] cockpit-po-plugin [ ] cockpit-rsync-plugin [ ] stylelint intergration [ ] fail on warnings eslint [0] [0] https://github.com/eslint/eslint/issues/16804 --- .eslintrc.json | 9 +- Makefile | 9 +- build.js | 65 +++++++++++++ esbuild-eslint-plugin.js | 28 ++++++ node_modules | 2 +- package.json | 48 +++------ packaging/cockpit-podman.spec.in | 2 +- webpack.config.js | 162 ------------------------------- 8 files changed, 118 insertions(+), 207 deletions(-) create mode 100644 build.js create mode 100644 esbuild-eslint-plugin.js delete mode 100644 webpack.config.js diff --git a/.eslintrc.json b/.eslintrc.json index df6fb5fe4..7460c7aa3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,16 +2,11 @@ "root": true, "env": { "browser": true, - "es6": true + "es2022": true }, "extends": ["eslint:recommended", "standard", "standard-jsx", "standard-react", "plugin:jsx-a11y/recommended"], - "parser": "@babel/eslint-parser", "parserOptions": { - "ecmaVersion": "7", - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module" + "ecmaVersion": 2022 }, "plugins": ["flowtype", "react", "react-hooks", "jsx-a11y"], "rules": { diff --git a/Makefile b/Makefile index 1bf57933e..b597f1b25 100644 --- a/Makefile +++ b/Makefile @@ -91,11 +91,12 @@ packaging/arch/PKGBUILD: packaging/arch/PKGBUILD.in packaging/debian/changelog: packaging/debian/changelog.in sed 's/VERSION/$(VERSION)/' $< > $@ -$(WEBPACK_TEST): $(COCKPIT_REPO_STAMP) $(shell find src/ -type f) package.json webpack.config.js - $(MAKE) package-lock.json && NODE_ENV=$(NODE_ENV) node_modules/.bin/webpack - # In development mode terser does not run and so no LICENSE.txt.gz is generated, so we explictly create it as it is required for building rpm's +$(WEBPACK_TEST): $(COCKPIT_REPO_STAMP) $(shell find src/ -type f) package.json build.js + $(MAKE) package-lock.json && NODE_ENV=$(NODE_ENV) npm run build + # In development mode minification does not run and so no LEGAL.txt is generated, so we explictly create it as it is required for building rpm's if [ "$$NODE_ENV" = "development" ]; then \ - gzip dist/index.js.LICENSE.txt.gz; \ + touch dist/index.js.LEGAL.txt; \ + touch dist/index.css.LEGAL.txt; \ fi watch: diff --git a/build.js b/build.js new file mode 100644 index 000000000..00cd4ded0 --- /dev/null +++ b/build.js @@ -0,0 +1,65 @@ +import copy from 'esbuild-plugin-copy'; +import fs from "fs"; +import esbuild from "esbuild"; +import path from "path"; +import { sassPlugin } from 'esbuild-sass-plugin'; +import { eslintPlugin } from './esbuild-eslint-plugin.js'; + +const useEslint = process.env.ESLINT !== '0'; +const production = process.env.NODE_ENV === 'production'; + +const nodePaths=['pkg/lib'] + +const context = await esbuild.context({ + // Cockpit's http server is not able to load multiple JS files + bundle: true, + entryPoints: ["./src/index.js"], + // Allow external font files which live in ../../static/fonts + external: ['*.woff', '*.woff2', '*.jpg', '*.svg', '../../assets*'], + // Move all legal comments to a .LEGAL.txt file + legalComments: 'external', + loader: { ".js": "jsx" }, + minify: production, + // List of directories to use when resolving import statements + nodePaths, + outdir: "./dist", + ... !production ? { sourcemap: "external" } : {}, + target: ['es2020'], + logLevel: "debug", + plugins: [ + ... useEslint ? [eslintPlugin] : [], + // 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'] }, + ] + }), + sassPlugin({ + loadPaths: [...nodePaths, 'node_modules'], quietDeps: true, + async transform(source, resolveDir, path) { + if (path.includes('patternfly-4-cockpit.scss')) { + return source + .replace(/url.*patternfly-icons-fake-path.*;/g, 'url("../base1/fonts/patternfly.woff") format("woff");') + .replace(/@font-face[^}]*patternfly-fonts-fake-path[^}]*}/g, ''); + } + return source; + } + }), + ] +}) + +// Manually do an incremental build +const result = await context.rebuild() + +/* development options for faster iteration */ +const watchMode = process.env.ESBUILD_WATCH === "true" || false; +if(watchMode) { + console.log("Running in watch mode"); + // Enable watch mode + await context.watch() +} +else { + context.dispose(); +} diff --git a/esbuild-eslint-plugin.js b/esbuild-eslint-plugin.js new file mode 100644 index 000000000..654e8d7dd --- /dev/null +++ b/esbuild-eslint-plugin.js @@ -0,0 +1,28 @@ +// FIXME: replace with plugin from npmjs if possible +// Candidate [1] https://github.com/to-codando/esbuild-plugin-linter/issues/1 +// Candidate [2] https://github.com/robinloeffel/esbuild-plugin-eslint/issues/5 + +import { ESLint } from 'eslint'; + +export const eslintPlugin = { + name: 'eslintPlugin', + setup(build) { + const filesToLint = []; + const eslint = new ESLint(); + const filter = /src\/.*\.(jsx?|js?)$/; + + build.onLoad({ filter }, ({ path }) => { + filesToLint.push(path); + }); + + build.onEnd(async () => { + const result = await eslint.lintFiles(filesToLint); + const formatter = await eslint.loadFormatter('stylish'); + const output = formatter.format(result); + if (output.length > 0) { + // eslint-disable-next-line no-console + console.log(output); + } + }); + }, +} diff --git a/node_modules b/node_modules index d92e684f9..366dde12c 160000 --- a/node_modules +++ b/node_modules @@ -1 +1 @@ -Subproject commit d92e684f9351e2b5618497373f9a45521028fc1d +Subproject commit 366dde12c5f9b815002d17992e8cbe29cc1b383b diff --git a/package.json b/package.json index 9d92f2018..1a8eb58de 100644 --- a/package.json +++ b/package.json @@ -1,57 +1,39 @@ { "name": "podman", "description": "Cockpit UI for Podman Containers", + "type": "module", "main": "index.js", "repository": "git@github.com:cockpit-project/cockpit-podman.git", "author": "", "license": "LGPL-2.1", "scripts": { - "watch": "webpack --watch --progress", - "build": "webpack", + "watch": "ESBUILD_WATCH='true' node build.js", + "build": "node build.js", "eslint": "eslint --ext .jsx --ext .js src/", "eslint:fix": "eslint --fix --ext .jsx --ext .js src/", "stylelint": "stylelint src/*{.css,scss}", "stylelint:fix": "stylelint --fix src/*{.css,scss}" }, "devDependencies": { - "@babel/core": "^7.6.0", - "@babel/eslint-parser": "^7.13.14", - "@babel/preset-env": "7.12.17", - "@babel/preset-react": "^7.0.0", "argparse": "^2.0.1", - "babel-loader": "^8.0.0", "chrome-remote-interface": "^0.31.2", - "compression-webpack-plugin": "^6.0.0", - "copy-webpack-plugin": "^6.1.0", - "css-loader": "^5.2.0", - "css-minimizer-webpack-plugin": "4.0.0", - "eslint": "^7.10.0", - "eslint-config-standard": "^16.0.0", - "eslint-config-standard-jsx": "^10.0.0", - "eslint-config-standard-react": "^11.0.1", - "eslint-plugin-flowtype": "^5.2.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.23.0", - "eslint-plugin-react-hooks": "^4.1.2", - "eslint-plugin-standard": "^4.0.1", - "eslint-webpack-plugin": "^2.5.3", + "esbuild-plugin-copy": "^2.0.2", + "eslint": "^8.29.0", + "eslint-config-standard": "^17.0.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-config-standard-react": "^13.0.0", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-standard": "^5.0.0", "gettext-parser": "^2.0.0", "htmlparser": "^1.7.7", "jed": "^1.1.1", - "mini-css-extract-plugin": "^0.11.0", "sass": "^1.35.1", - "sass-loader": "^12.1.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.3", - "webpack": "^5.31.0", - "webpack-cli": "^4.6.0" + "stylelint-config-standard-scss": "^5.0.0" }, "dependencies": { "@patternfly/patternfly": "4.224.2", @@ -60,6 +42,8 @@ "@patternfly/react-table": "4.112.39", "date-fns": "2.28.0", "docker-names": "1.2.1", + "esbuild": "0.17.7", + "esbuild-sass-plugin": "2.4.5", "prop-types": "15.8.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/packaging/cockpit-podman.spec.in b/packaging/cockpit-podman.spec.in index 8cafa1283..ab6a54654 100644 --- a/packaging/cockpit-podman.spec.in +++ b/packaging/cockpit-podman.spec.in @@ -53,7 +53,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/webpack.config.js b/webpack.config.js deleted file mode 100644 index 2f9a40e46..000000000 --- a/webpack.config.js +++ /dev/null @@ -1,162 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const copy = require("copy-webpack-plugin"); -const extract = require("mini-css-extract-plugin"); -const TerserJSPlugin = require('terser-webpack-plugin'); -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); -const CompressionPlugin = require("compression-webpack-plugin"); -const ESLintPlugin = require('eslint-webpack-plugin'); -const CockpitPoPlugin = require("./pkg/lib/cockpit-po-plugin"); -const CockpitRsyncPlugin = require("./pkg/lib/cockpit-rsync-plugin"); -const StylelintPlugin = require('stylelint-webpack-plugin'); - -/* A standard nodejs and webpack pattern */ -const production = process.env.NODE_ENV === 'production'; - -/* development options for faster iteration */ -const eslint = process.env.ESLINT !== '0'; - -/* Default to disable csslint for faster production builds */ -const stylelint = process.env.STYLELINT ? (process.env.STYLELINT !== '0') : !production; - -// Obtain package name from package.json -const packageJson = JSON.parse(fs.readFileSync('package.json')); - -// Non-JS files which are copied verbatim to dist/ -const copy_files = [ - "./src/index.html", - "./src/manifest.json", -]; - -const plugins = [ - new copy({ patterns: copy_files }), - new extract({filename: "[name].css"}), - new CockpitPoPlugin(), - new CockpitRsyncPlugin({dest: packageJson.name}), -]; - -if (eslint) { - plugins.push(new ESLintPlugin({ extensions: ["js", "jsx"], failOnWarning: true, })); -} - -if (stylelint) { - plugins.push(new StylelintPlugin({ - context: "src/", - })); -} - -/* Only minimize when in production mode */ -if (production) { - plugins.unshift(new CompressionPlugin({ - test: /\.(js|html|css)$/, - deleteOriginalAssets: true - })); -} - -module.exports = { - mode: production ? 'production' : 'development', - resolve: { - modules: [ "node_modules", path.resolve(__dirname, 'pkg/lib') ], - alias: { 'font-awesome': 'font-awesome-sass/assets/stylesheets' }, - }, - resolveLoader: { - modules: [ "node_modules", path.resolve(__dirname, 'pkg/lib') ], - }, - watchOptions: { - ignored: /node_modules/, - }, - entry: { - index: "./src/index.js", - }, - devtool: "source-map", - stats: "errors-warnings", - - optimization: { - minimize: production, - minimizer: [ - new TerserJSPlugin({ - extractComments: { - condition: true, - filename: `[file].LICENSE.txt?query=[query]&filebase=[base]`, - banner(licenseFile) { - return `License information can be found in ${licenseFile}`; - }, - }, - }), - new CssMinimizerPlugin() - ], - }, - - module: { - rules: [ - { - exclude: /node_modules/, - use: "babel-loader", - test: /\.(js|jsx)$/ - }, - /* HACK: remove unwanted fonts from PatternFly's css */ - { - test: /patternfly-4-cockpit.scss$/, - use: [ - extract.loader, - { - loader: 'css-loader', - options: { - sourceMap: true, - url: false, - }, - }, - { - loader: 'string-replace-loader', - options: { - multiple: [ - { - search: /src:url\("patternfly-icons-fake-path\/pficon[^}]*/g, - replace: 'src:url("../base1/fonts/patternfly.woff") format("woff");', - }, - { - search: /@font-face[^}]*patternfly-fonts-fake-path[^}]*}/g, - replace: '', - }, - ] - }, - }, - { - loader: 'sass-loader', - options: { - sourceMap: !production, - sassOptions: { - outputStyle: production ? 'compressed' : undefined, - }, - }, - }, - ] - }, - { - test: /\.s?css$/, - exclude: /patternfly-4-cockpit.scss/, - use: [ - extract.loader, - { - loader: 'css-loader', - options: { - sourceMap: true, - url: false - } - }, - { - loader: 'sass-loader', - options: { - sourceMap: !production, - sassOptions: { - outputStyle: production ? 'compressed' : undefined, - }, - }, - }, - ] - }, - ] - }, - plugins: plugins -}