diff --git a/.eslintrc.js b/.eslintrc.js index 7d2c351b63..0e92f4e086 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,11 +61,11 @@ module.exports = { }, env: { browser: true, - node: true, + // node: true, jquery: true, }, parserOptions: { - project: './tsconfig.json', + project: './tsconfig.eslint.json', }, settings: { 'import/resolver': { @@ -74,4 +74,16 @@ module.exports = { }, }, }, + overrides: [ + // Tests are completely different + // And we shouldn't be so strict + { + files: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], + plugins: ['jest'], + env: { + node: true, + 'jest/globals': true, + }, + }, + ], }; diff --git a/jest.config.js b/jest.config.js index 08199748a1..e8e677b90f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -25,6 +25,7 @@ module.exports = { ], globals: { 'ts-jest': { + tsconfig: `tsconfig.test.json`, diagnostics: { // https://github.com/kulshekhar/ts-jest/issues/1647#issuecomment-832577036 pathRegex: /\.(test)\.tsx$/, diff --git a/package.json b/package.json index 8ae7416262..e35139e64b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build": "NODE_ENV=production webpack --config scripts/webpack/webpack.prod.ts", "build:panel": "NODE_ENV=production webpack --config scripts/webpack/webpack.panel.ts", "build:size-limit": "NODE_ENV=production webpack --config scripts/webpack/webpack.size-limit.ts", - "test": "jest", + "test": "jest --runInBand", "test:ss": "UPDATE_SNAPSHOTS=true ./scripts/jest-snapshots/run-docker.sh", "test:ss-check": "./scripts/jest-snapshots/run-docker.sh", "lint": "eslint ./ --ext .js,.jsx,.ts,.tsx --cache --fix", @@ -87,6 +87,7 @@ "eslint-plugin-css-modules": "^2.11.0", "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^25.3.4", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-react": "^7.21.5", @@ -97,7 +98,6 @@ "jest-canvas-mock": "^2.3.1", "jest-css-modules-transform": "^4.3.0", "jest-image-snapshot": "^4.5.1", - "koa-route": "^3.2.0", "lint-staged": "^11.1.2", "monaco-editor-webpack-plugin": "^1.9.0", "optimize-css-assets-webpack-plugin": "^6.0.1", @@ -108,15 +108,14 @@ "sass": "^1.26.10", "size-limit": "^6.0.3", "svg-jest": "^1.0.1", - "sync-request": "^6.1.0", "ts-jest": "^27.0.5", "typescript": "^4.5.2", "typescript-plugin-css-modules": "^3.4.0", "webpack": "^5.64.0", "webpack-bundle-analyzer": "^4.4.2", + "webpack-livereload-plugin": "^3.0.2", "webpack-merge": "^5.0.9", - "webpack-plugin-hash-output": "^3.2.1", - "webpack-plugin-serve": "^1.5.0" + "webpack-plugin-hash-output": "^3.2.1" }, "dependencies": { "@babel/plugin-transform-runtime": "^7.16.4", @@ -149,6 +148,7 @@ "copy-webpack-plugin": "^6.3.2", "css-loader": "^4.0.0", "date-fns": "^2.27.0", + "esbuild-loader": "^2.18.0", "eslint-import-resolver-webpack": "^0.13.2", "file-loader": "^6.2.0", "glob": "^7.1.7", diff --git a/scripts/webpack/shared.ts b/scripts/webpack/shared.ts index 90926b6372..1ca62fa1d5 100644 --- a/scripts/webpack/shared.ts +++ b/scripts/webpack/shared.ts @@ -61,35 +61,42 @@ export function getJsLoader() { exclude: /node_modules/, use: [ { - loader: 'babel-loader', + loader: 'esbuild-loader', options: { - cacheDirectory: true, - babelrc: true, - - plugins: ['@babel/plugin-transform-runtime'], - // Note: order is bottom-to-top and/or right-to-left - presets: [ - [ - '@babel/preset-env', - { - targets: { - browsers: 'last 3 versions', - }, - useBuiltIns: 'entry', - corejs: 3, - modules: false, - }, - ], - [ - '@babel/preset-typescript', - { - allowNamespaces: true, - }, - ], - '@babel/preset-react', - ], + loader: 'tsx', // Or 'ts' if you don't need tsx + target: 'es2015', }, }, + // { + // loader: 'babel-loader', + // options: { + // cacheDirectory: true, + // babelrc: true, + // + // plugins: ['@babel/plugin-transform-runtime'], + // // Note: order is bottom-to-top and/or right-to-left + // presets: [ + // [ + // '@babel/preset-env', + // { + // targets: { + // browsers: 'last 3 versions', + // }, + // useBuiltIns: 'entry', + // corejs: 3, + // modules: false, + // }, + // ], + // [ + // '@babel/preset-typescript', + // { + // allowNamespaces: true, + // }, + // ], + // '@babel/preset-react', + // ], + // }, + // }, ], }, ]; diff --git a/scripts/webpack/webpack.common.ts b/scripts/webpack/webpack.common.ts index 1f1c0582c2..8eb7badb14 100644 --- a/scripts/webpack/webpack.common.ts +++ b/scripts/webpack/webpack.common.ts @@ -5,12 +5,23 @@ import fs from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import CopyPlugin from 'copy-webpack-plugin'; +import { ESBuildMinifyPlugin } from 'esbuild-loader'; import { getAlias, getJsLoader, getStyleLoaders } from './shared'; const pages = glob - .sync('./webapp/templates/*.html') + // Most of the cases we will be developing the SPA + // So it makes sense only building it + // We go from + // [webpack.Progress] | | 1131 ms asset processing > HtmlWebpackPlugin + // To [webpack.Progress] | | 215 ms asset processing > HtmlWebpackPlugin + .sync( + process.env.NODE_ENV === 'production' + ? './webapp/templates/*.html' + : './webapp/templates/index.html' + ) .map((x) => path.basename(x)); + const pagePlugins = pages.map( (name) => new HtmlWebpackPlugin({ @@ -68,6 +79,15 @@ export default { ignored: /node_modules/, }, + optimization: { + minimizer: [ + new ESBuildMinifyPlugin({ + target: 'es2015', + css: true, + }), + ], + }, + module: { // Note: order is bottom-to-top and/or right-to-left rules: [ @@ -100,10 +120,6 @@ export default { new MiniCssExtractPlugin({ filename: '[name].[hash].css', }), - new webpack.IgnorePlugin({ - resourceRegExp: /^\.\/locale$/, - contextRegExp: /moment$/, - }), new CopyPlugin({ patterns: [ { diff --git a/scripts/webpack/webpack.dev.ts b/scripts/webpack/webpack.dev.ts index e2a974ce0a..0103e912dc 100644 --- a/scripts/webpack/webpack.dev.ts +++ b/scripts/webpack/webpack.dev.ts @@ -1,97 +1,13 @@ import { merge } from 'webpack-merge'; -import { WebpackPluginServe } from 'webpack-plugin-serve'; -import path from 'path'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import request from 'sync-request'; -import fs from 'fs'; -import route from 'koa-route'; +import LiveReloadPlugin from 'webpack-livereload-plugin'; import common from './webpack.common'; module.exports = merge(common, { devtool: 'eval-source-map', mode: 'development', - entry: { - serve: 'webpack-plugin-serve/client', - }, - plugins: [ - // create a server on port 4041 with live reload - // it will serve all static assets com webapp/public/assets - // and for the endpoints it will redirect to the go server (on port 4040) - new WebpackPluginServe({ - port: 4041, - static: path.resolve(__dirname, '../../webapp/public'), - liveReload: true, - waitForBuild: true, - middleware: (app, builtins) => { - // TODO - // this sucks, maybe update endpoints to prefix with /api? - app.use(builtins.proxy('/render', { target: 'http://localhost:4040' })); - app.use( - builtins.proxy('/render-diff', { target: 'http://localhost:4040' }) - ); - app.use(builtins.proxy('/labels', { target: 'http://localhost:4040' })); - app.use( - builtins.proxy('/labels-diff', { target: 'http://localhost:4040' }) - ); - app.use( - builtins.proxy('/label-values', { target: 'http://localhost:4040' }) - ); - - // New Endpoints are implemented under /api - app.use(builtins.proxy('/api', { target: 'http://localhost:4040' })); - - // serve index for all pages - // that are not static (.css, .js) nor live reload (/wps) - // TODO: simplify this - app.use( - route.get(/^(.(?!(\.js|\.css|\.svg|wps)$))+$/, (ctx) => { - ctx.body = fs.readFileSync( - path.resolve(__dirname, '../../webapp/public/assets/index.html'), - { - encoding: 'utf-8', - } - ); - }) - ); - }, - }), - - // serve index.html from the go server - // and additionally inject anything else required (eg livereload ws) - new HtmlWebpackPlugin({ - publicPath: '/assets', - templateContent: () => { - let res; - - // TODO: accept this to be overwritten? - // that's useful for when running on a different port (when you are running multiple pyroscope versions locally) - // or when running on ipv6 - const goServerAddr = 'http://localhost:4040'; - - try { - console.log(`Trying to access go server on ${goServerAddr}`); - - // makes a request against the go server to retrieve its index.html - // it assumes the server will either not respond or respond with 2xx - // (ie it doesn't handle != 2xx status codes) - // https://www.npmjs.com/package/sync-request - res = request('GET', goServerAddr, { - timeout: 1000, - maxRetries: 30, - retryDelay: 1000, - retry: true, - }); - } catch (e) { - throw new Error( - `Could not find pyroscope instance running on ${goServerAddr}. Make sure you have pyroscope server running on port :4040` - ); - } - - console.log('Live reload server is up'); - - return res.getBody('utf8'); - }, + new LiveReloadPlugin({ + appendScriptTag: true, }), ], // TODO deal with these types diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000000..9a0d2e6013 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["."], + // Since we excluded the test files in the main tsconfig.json + // We must renable them by explicitly setting an 'exclude' + "exclude": ["node_modules"] +} diff --git a/tsconfig.json b/tsconfig.json index 12970c507c..13236fc3fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,13 +8,27 @@ "esModuleInterop": true, "sourceMap": true, "allowSyntheticDefaultImports": true, + "skipLibCheck": true, "paths": { "@utils/*": ["./webapp/javascript/util/*"], "@models/*": ["./webapp/javascript/models/*"], "@ui/*": ["./webapp/javascript/ui/*"], "@pyroscope/redux/*": ["./webapp/javascript/redux/*"] }, + "types": ["node"], "plugins": [{ "name": "typescript-plugin-css-modules" }] }, - "exclude": ["node_modules"] + // ts-node is currently only used by webpack + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + }, + "include": ["./webapp/javascript/"], + "exclude": [ + "webapp/javascript/**/*.spec.ts", + "webapp/javascript/**/*.spec.tsx", + "**/node_modules", + "**/.*/" + ] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000000..45c64b0de8 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "skipLibCheck": true, + "types": ["jest", "mocha", "@testing-library/jest-dom"], + "sourceMap": false + } +} diff --git a/webapp/README.md b/webapp/README.md index f506d18cb9..5906fbf96b 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -38,3 +38,12 @@ To be able to do that, you need to add the alias to the following files: * `scripts/webpack/webpack.common.js` * `tsconfig.json` * `jest.config.js` + +# Developing the webapp/templates page +By default, developing pages other than the index require a bit of setup: + + +For example, acessing http://locahlost:4040/forbidden won't work +To be able to access it, update the variable `pages` in `scripts/webpack.common.ts` to allow building all pages when in dev mode. + +Beware, this will make the (local) build slower. diff --git a/webapp/javascript/components/FileUploader.tsx b/webapp/javascript/components/FileUploader.tsx index 7f43363867..4a11aa97af 100644 --- a/webapp/javascript/components/FileUploader.tsx +++ b/webapp/javascript/components/FileUploader.tsx @@ -28,6 +28,10 @@ export default function FileUploader({ file, setFile, className }: Props) { reader.onload = () => { const binaryStr = reader.result; + if (typeof binaryStr === 'string') { + throw new Error('Expecting file in binary format but got a string'); + } + try { // ArrayBuffer -> JSON const s = JSON.parse( @@ -85,7 +89,7 @@ export default function FileUploader({ file, setFile, className }: Props) { {file && (