diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 558805e5e1bd23..89f403268d51b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -134,7 +134,6 @@ apps/recipes-react-components @microsoft/cxe-red @microsoft/cxe-coastal @microso #### Packages packages/azure-themes @Jacqueline-ms @robtaft-ms -packages/bundle-size @microsoft/teams-prg packages/react-conformance @microsoft/fluentui-react-build packages/date-time-utilities @microsoft/cxe-red packages/eslint-plugin @microsoft/fluentui-react-build diff --git a/package.json b/package.json index ec275943525372..b9925dc60b18f8 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.3", - "@azure/data-tables": "13.0.0", "@babel/core": "7.21.8", "@babel/generator": "7.21.5", "@babel/parser": "7.21.8", @@ -170,7 +169,6 @@ "@types/markdown-table": "2.0.0", "@types/micromatch": "4.0.2", "@types/node": "16.18.1", - "@types/node-fetch": "2.5.7", "@types/prettier": "2.7.2", "@types/progress": "2.0.5", "@types/prompts": "2.4.1", @@ -211,7 +209,6 @@ "circular-dependency-plugin": "5.2.2", "clean-css": "4.2.3", "clean-webpack-plugin": "4.0.0", - "cli-table3": "0.6.3", "connect-history-api-fallback": "1.6.0", "copy-webpack-plugin": "8.1.0", "cross-env": "^5.1.4", @@ -259,7 +256,6 @@ "gulp-remember": "1.0.1", "gulp-sourcemaps": "2.6.5", "gulp-util": "3.0.8", - "gzip-size": "6.0.0", "html-webpack-plugin": "5.1.0", "ignore-not-found-export-webpack-plugin": "1.0.2", "imports-loader": "1.2.0", @@ -286,7 +282,6 @@ "mini-css-extract-plugin": "2.6.1", "monosize": "0.1.0", "monosize-storage-azure": "0.0.3", - "node-fetch": "2.6.7", "node-plop": "0.25.0", "node-polyfill-webpack-plugin": "1.0.2", "nx": "15.9.4", @@ -300,7 +295,6 @@ "postcss-loader": "4.1.0", "postcss-modules": "4.1.3", "prettier": "2.8.8", - "pretty-bytes": "5.6.0", "progress": "2.0.3", "prompts": "2.4.2", "puppeteer": "19.6.0", diff --git a/packages/bundle-size/.eslintrc.json b/packages/bundle-size/.eslintrc.json deleted file mode 100644 index f502b07b22439c..00000000000000 --- a/packages/bundle-size/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["plugin:@fluentui/eslint-plugin/node"], - "root": true, - "rules": { - "import/no-extraneous-dependencies": ["error", { "packageDir": ["../../", "./"] }] - } -} diff --git a/packages/bundle-size/CHANGELOG.json b/packages/bundle-size/CHANGELOG.json deleted file mode 100644 index f06d8af733cded..00000000000000 --- a/packages/bundle-size/CHANGELOG.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "name": "@fluentui/bundle-size", - "entries": [ - { - "date": "Thu, 01 Dec 2022 07:57:05 GMT", - "tag": "@fluentui/bundle-size_v1.2.0", - "version": "1.2.0", - "comments": { - "none": [ - { - "author": "martinhochel@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "c46ff8dec351ebf57efb4114366016010b285429", - "comment": "chore: enable isolateModules" - } - ] - } - }, - { - "date": "Thu, 10 Nov 2022 07:55:03 GMT", - "tag": "@fluentui/bundle-size_v1.2.0", - "version": "1.2.0", - "comments": { - "minor": [ - { - "author": "martinhochel@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "87859b052155ca206ef3540a2a9623803f6e2b93", - "comment": "fix(typings): fix type errors exposed by node 14 typings" - } - ] - } - }, - { - "date": "Wed, 28 Sep 2022 07:37:48 GMT", - "tag": "@fluentui/bundle-size_v1.1.7", - "version": "1.1.7", - "comments": { - "patch": [ - { - "author": "martinhochel@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "b7fc0c2998a2ed7584b6b7ca8a969b17793afd28", - "comment": "fix: bump workspace-tools and add options to collectLocalReport api" - } - ] - } - }, - { - "date": "Thu, 12 May 2022 07:41:44 GMT", - "tag": "@fluentui/bundle-size_v1.1.6", - "version": "1.1.6", - "comments": { - "patch": [ - { - "author": "olfedias@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "6cc697e86b48eddb8bdadd3bcff72594305c72de", - "comment": "fix: update glob for collecting reports" - } - ] - } - }, - { - "date": "Thu, 21 Apr 2022 07:36:48 GMT", - "tag": "@fluentui/bundle-size_v1.1.5", - "version": "1.1.5", - "comments": { - "patch": [ - { - "author": "olfedias@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "d1ac3a2a447caff9c48724a964cf736d4b2678ce", - "comment": "fix: update icons in Markdown reporter" - } - ] - } - }, - { - "date": "Wed, 15 Dec 2021 07:31:27 GMT", - "tag": "@fluentui/bundle-size_v1.1.4", - "version": "1.1.4", - "comments": { - "none": [ - { - "author": "martinhochel@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "28ceaaa83cd92a0389c466f0b15b283e3d9b08e4", - "comment": "fixup! fixup! chore: update all jest deps to v25 + apply single version policy" - } - ] - } - }, - { - "date": "Mon, 13 Dec 2021 07:27:44 GMT", - "tag": "@fluentui/bundle-size_v1.1.4", - "version": "1.1.4", - "comments": { - "none": [ - { - "author": "lingfangao@hotmail.com", - "package": "@fluentui/bundle-size", - "commit": "e8092e63498f4d42c54a11325172dd3e6f2d65d6", - "comment": "fix: incorrect promise test" - } - ] - } - }, - { - "date": "Fri, 03 Dec 2021 07:36:11 GMT", - "tag": "@fluentui/bundle-size_v1.1.4", - "version": "1.1.4", - "comments": { - "patch": [ - { - "author": "bsunderhus@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "2f1e97b40799b217c781df5df68434721b331c0d", - "comment": "Adds missing dependencies to bundle-size" - } - ] - } - }, - { - "date": "Thu, 25 Nov 2021 14:54:12 GMT", - "tag": "@fluentui/bundle-size_v1.1.3", - "version": "1.1.3", - "comments": { - "patch": [ - { - "author": "bsunderhus@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "d35f28080d37c8026e7d407d368946e01f17cf2e", - "comment": "Adds missing dependencies to bundle-size tool" - } - ] - } - }, - { - "date": "Wed, 17 Nov 2021 07:36:21 GMT", - "tag": "@fluentui/bundle-size_v1.1.2", - "version": "1.1.2", - "comments": { - "patch": [ - { - "author": "bsunderhus@microsoft.com", - "package": "@fluentui/bundle-size", - "commit": "a75c8c07c5296206b998f9500baced6952e3f81b", - "comment": "Adds verification to compilation error" - } - ] - } - }, - { - "date": "Mon, 27 Sep 2021 07:34:24 GMT", - "tag": "@fluentui/bundle-size_v1.1.1", - "version": "1.1.1", - "comments": { - "patch": [ - { - "author": "Humberto.Morimoto@microsoft.com", - "package": "@fluentui/bundle-size", - "comment": "bundle-size: Fixing issue where the baseline and PR values were flipped.", - "commit": "16bc6ece02c11f68d9e7d4bf1e5547b6a2123135" - } - ] - } - }, - { - "date": "Tue, 20 Jul 2021 22:23:17 GMT", - "tag": "@fluentui/bundle-size_v1.1.0", - "version": "1.1.0", - "comments": { - "none": [ - { - "comment": "Disable readConfig tests to unblock CI", - "author": "lingfan.gao@microsoft.com", - "commit": "400040ac767e7660922ac19c11955f02ce53a0bd", - "package": "@fluentui/bundle-size" - } - ], - "minor": [ - { - "comment": "feat(bundle-size): Allow custom webpack config, prepare publish", - "author": "lingfan.gao@microsoft.com", - "commit": "639b8f90c61aeb115bb0e893c70c87de441fd21e", - "package": "@fluentui/bundle-size" - } - ] - } - } - ] -} diff --git a/packages/bundle-size/CHANGELOG.md b/packages/bundle-size/CHANGELOG.md deleted file mode 100644 index 76d37564d2d418..00000000000000 --- a/packages/bundle-size/CHANGELOG.md +++ /dev/null @@ -1,85 +0,0 @@ -# Change Log - @fluentui/bundle-size - -This log was last generated on Thu, 10 Nov 2022 07:55:03 GMT and should not be manually modified. - - - -## [1.2.0](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.2.0) - -Thu, 10 Nov 2022 07:55:03 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.7..@fluentui/bundle-size_v1.2.0) - -### Minor changes - -- fix(typings): fix type errors exposed by node 14 typings ([PR #25510](https://github.com/microsoft/fluentui/pull/25510) by martinhochel@microsoft.com) - -## [1.1.7](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.7) - -Wed, 28 Sep 2022 07:37:48 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.6..@fluentui/bundle-size_v1.1.7) - -### Patches - -- fix: bump workspace-tools and add options to collectLocalReport api ([PR #24891](https://github.com/microsoft/fluentui/pull/24891) by martinhochel@microsoft.com) - -## [1.1.6](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.6) - -Thu, 12 May 2022 07:41:44 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.5..@fluentui/bundle-size_v1.1.6) - -### Patches - -- fix: update glob for collecting reports ([PR #22937](https://github.com/microsoft/fluentui/pull/22937) by olfedias@microsoft.com) - -## [1.1.5](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.5) - -Thu, 21 Apr 2022 07:36:48 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.4..@fluentui/bundle-size_v1.1.5) - -### Patches - -- fix: update icons in Markdown reporter ([PR #22543](https://github.com/microsoft/fluentui/pull/22543) by olfedias@microsoft.com) - -## [1.1.4](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.4) - -Fri, 03 Dec 2021 07:36:11 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.3..@fluentui/bundle-size_v1.1.4) - -### Patches - -- Adds missing dependencies to bundle-size ([PR #20885](https://github.com/microsoft/fluentui/pull/20885) by bsunderhus@microsoft.com) - -## [1.1.3](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.3) - -Thu, 25 Nov 2021 14:54:12 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.2..@fluentui/bundle-size_v1.1.3) - -### Patches - -- Adds missing dependencies to bundle-size tool ([PR #20612](https://github.com/microsoft/fluentui/pull/20612) by bsunderhus@microsoft.com) - -## [1.1.2](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.2) - -Wed, 17 Nov 2021 07:36:21 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.1..@fluentui/bundle-size_v1.1.2) - -### Patches - -- Adds verification to compilation error ([PR #20613](https://github.com/microsoft/fluentui/pull/20613) by bsunderhus@microsoft.com) - -## [1.1.1](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.1) - -Mon, 27 Sep 2021 07:34:24 GMT -[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/bundle-size_v1.1.0..@fluentui/bundle-size_v1.1.1) - -### Patches - -- bundle-size: Fixing issue where the baseline and PR values were flipped. ([PR #19843](https://github.com/microsoft/fluentui/pull/19843) by Humberto.Morimoto@microsoft.com) - -## [1.1.0](https://github.com/microsoft/fluentui/tree/@fluentui/bundle-size_v1.1.0) - -Tue, 20 Jul 2021 22:23:17 GMT - -### Minor changes - -- feat(bundle-size): Allow custom webpack config, prepare publish ([PR #18705](https://github.com/microsoft/fluentui/pull/18705) by lingfan.gao@microsoft.com) diff --git a/packages/bundle-size/README.md b/packages/bundle-size/README.md deleted file mode 100644 index e17259dc31fd4b..00000000000000 --- a/packages/bundle-size/README.md +++ /dev/null @@ -1,103 +0,0 @@ -## `@fluentui/bundle-size` - -A CLI tool to measure bundle size locally and on CI. - -> NOTE: `bundle-size` requires to build packages first before doing any kind of measurements. Make sure to accommodate this in your pipeline - -### Usage - -#### Fixtures - -Fixtures declare exports that should be measured by the `bundle-size` tool. Fixtures are created inside each package. - -For example: - -```js -import { Component } from '@fluentui/react-component'; - -console.log(Component); -// 👆 "console.log()" is the easiest way to prevent tree-shaking - -export default { - name: 'Component', - // 👆 defines a name for story that will be used in output -}; -``` - -### Configuration - -For custom advanced behavior of `bundle-size`, you can create a `bundle-size.config.js` in the root of your project directory (next to `package.json`). - -``` -my-proj/ -├─ src/ -├─ bundle-size.config.js -├─ node_modules/ -├─ bundle-size/ -│ ├─ Fixture.fixture.js -├─ package.json -``` - -A global configuration can also be used for monorepo scenarios - -``` -my-proj-a/ -├─ src/ -├─ node_modules/ -├─ bundle-size/ -│ ├─ Fixture.fixture.js -├─ package.json -my-proj-b/ -├─ src/ -├─ node_modules/ -├─ bundle-size/ -│ ├─ Fixture.fixture.js -├─ package.json -bundle-size.config.js - -``` - -#### Config API - -```js -module.exports = { - webpack: config => { - // customize config here - return config; - }, -}; -``` - -### Commands - -#### `compare-reports` - -Compares local (requires call of `bundle-size measure` first) and remote results, provides output to CLI or to a Markdown file. - -```sh -yarn bundle-size compare-reports --branch=main --output=["cli"|"markdown"] [--quiet] -``` - -#### `measure` - -```sh -yarn bundle-size measure [--quiet] -``` - -Builds fixtures and produces artifacts. For each fixture: - -- `[fixture].fixture.js` - a modified fixture without a default export, used by a bundler -- `[fixture].output.js` - a partially minified file, useful for debugging -- `[fixture].min.js` - a fully minified file, used for measurements - -A report file `bundle-size.json` that is used by other steps. - -#### `upload-report` - -```sh -yarn bundle-size upload-report --branch=main --commit-sha=HASH [--quiet] -``` - -Aggregates local results to a single report and uploads data to Azure Table Storage. - -> NOTE: should be called only during CI builds. diff --git a/packages/bundle-size/__fixture__/sampleComparedReport.js b/packages/bundle-size/__fixture__/sampleComparedReport.js deleted file mode 100644 index 3a7978dac98ef6..00000000000000 --- a/packages/bundle-size/__fixture__/sampleComparedReport.js +++ /dev/null @@ -1,41 +0,0 @@ -const { emptyDiff } = require('../src/utils/compareResultsInReports'); - -/** @type {import('../src/utils/compareResultsInReports').ComparedReport} */ -const sampleComparedReport = [ - { - packageName: 'foo-package', - name: 'New entry', - path: 'foo.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - diff: emptyDiff, - }, - { - packageName: 'bar-package', - name: 'An entry without diff', - path: 'bar.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - diff: { - empty: false, - - minified: { delta: 0, percent: '0%' }, - gzip: { delta: 0, percent: '0%' }, - }, - }, - { - packageName: 'baz-package', - name: 'An entry with diff', - path: 'baz.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - diff: { - empty: false, - - minified: { delta: 1000, percent: '100%' }, - gzip: { delta: 100, percent: '100%' }, - }, - }, -]; - -module.exports = sampleComparedReport; diff --git a/packages/bundle-size/__fixture__/sampleReport.js b/packages/bundle-size/__fixture__/sampleReport.js deleted file mode 100644 index e5a83d071cef63..00000000000000 --- a/packages/bundle-size/__fixture__/sampleReport.js +++ /dev/null @@ -1,26 +0,0 @@ -/** @type {import('../src/utils/collectLocalReport').BundleSizeReport} */ -const sampleReport = [ - { - packageName: 'foo-package', - name: 'New entry', - path: 'foo.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - }, - { - packageName: 'bar-package', - name: 'An entry without diff', - path: 'bar.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - }, - { - packageName: 'baz-package', - name: 'An entry with diff', - path: 'baz.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - }, -]; - -module.exports = sampleReport; diff --git a/packages/bundle-size/bin/bundle-size.js b/packages/bundle-size/bin/bundle-size.js deleted file mode 100755 index 3a54e2fe01bd2c..00000000000000 --- a/packages/bundle-size/bin/bundle-size.js +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -require('../src/index'); diff --git a/packages/bundle-size/config/tests.js b/packages/bundle-size/config/tests.js deleted file mode 100644 index 9f9b1e52befc8c..00000000000000 --- a/packages/bundle-size/config/tests.js +++ /dev/null @@ -1,5 +0,0 @@ -const tmp = require('tmp'); - -// Clean up created files/folders on exit, even after exceptions -// (will not catch SIGINT on windows) -tmp.setGracefulCleanup(); diff --git a/packages/bundle-size/jest.config.js b/packages/bundle-size/jest.config.js deleted file mode 100644 index 824142713a3a63..00000000000000 --- a/packages/bundle-size/jest.config.js +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-check - -/** - * @type {import('@jest/types').Config.InitialOptions} - */ -module.exports = { - displayName: 'bundle-size', - preset: '../../jest.preset.js', - coverageDirectory: './coverage', - testEnvironment: 'node', - setupFilesAfterEnv: ['./config/tests.js'], -}; diff --git a/packages/bundle-size/package.json b/packages/bundle-size/package.json deleted file mode 100644 index 24dc2a3f2f52d3..00000000000000 --- a/packages/bundle-size/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@fluentui/bundle-size", - "version": "1.2.0", - "bin": { - "bundle-size": "./bin/bundle-size.js" - }, - "scripts": { - "lint": "eslint . --ext .js", - "test": "jest", - "type-check": "tsc" - }, - "dependencies": { - "@azure/data-tables": "^13.0.0", - "@babel/core": "^7.10.4", - "ajv": "^8.4.0", - "chalk": "^4.1.0", - "cli-table3": "^0.6.3", - "del": "^6.0.0", - "find-up": "^5.0.0", - "glob": "^7.1.2", - "gzip-size": "^6.0.0", - "prettier": "^2.2.1", - "pretty-bytes": "^5.6.0", - "terser": "^5.5.1", - "webpack": "^5.21.2", - "workspace-tools": "^0.27.0", - "ci-info": "^3.8.0", - "node-fetch": "^2.6.1", - "yargs": "^13.3.2" - } -} diff --git a/packages/bundle-size/project.json b/packages/bundle-size/project.json deleted file mode 100644 index 4005cabde649b3..00000000000000 --- a/packages/bundle-size/project.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@fluentui/bundle-size", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "sourceRoot": "packages/bundle-size/src", - "tags": ["platform:node"], - "implicitDependencies": [] -} diff --git a/packages/bundle-size/src/commands/compareReports.js b/packages/bundle-size/src/commands/compareReports.js deleted file mode 100644 index c281014091c2fa..00000000000000 --- a/packages/bundle-size/src/commands/compareReports.js +++ /dev/null @@ -1,82 +0,0 @@ -const chalk = require('chalk'); - -const cliReporter = require('../reporters/cliReporter'); -const markdownReporter = require('../reporters/markdownReporter'); -const collectLocalReport = require('../utils/collectLocalReport'); -const { compareResultsInReports } = require('../utils/compareResultsInReports'); -const getRemoteReport = require('../utils/getRemoteReport'); -const { hrToSeconds } = require('../utils/helpers'); - -/** - * @param {typeof import('../index') & { branch: string, output: 'cli' | 'markdown' }} options - */ -async function compareReports(options) { - const { branch, output, quiet } = options; - const startTime = process.hrtime(); - - const localReportStartTime = process.hrtime(); - const localReport = await collectLocalReport(); - - if (!quiet) { - console.log( - [chalk.blue('[i]'), `Local report prepared in ${hrToSeconds(process.hrtime(localReportStartTime))}`].join(' '), - ); - } - - const remoteReportStartTime = process.hrtime(); - const { commitSHA, remoteReport } = await getRemoteReport(branch); - - if (!quiet) { - if (commitSHA === '') { - console.log([chalk.blue('[i]'), `Remote report for "${branch}" branch was not found`].join(' ')); - } else { - console.log( - [ - chalk.blue('[i]'), - `Remote report for "${commitSHA}" commit fetched in ${hrToSeconds(process.hrtime(remoteReportStartTime))}`, - ].join(' '), - ); - } - } - - const result = compareResultsInReports(localReport, remoteReport); - - switch (output) { - case 'cli': - await cliReporter(result); - break; - case 'markdown': - await markdownReporter(result, commitSHA, quiet); - break; - } - - if (!quiet) { - console.log(`Completed in ${hrToSeconds(process.hrtime(startTime))}`); - } -} - -// --- - -/** @type {import('yargs').CommandModule} */ -const api = { - command: 'compare-reports', - describe: 'compares local and remote results', - builder: { - branch: { - alias: 'b', - type: 'string', - description: 'A branch to compare against', - default: 'main', - }, - output: { - alias: 'o', - type: 'string', - choices: ['cli', 'markdown'], - description: 'Defines a reporter to produce output', - default: 'cli', - }, - }, - handler: compareReports, -}; - -module.exports = api; diff --git a/packages/bundle-size/src/commands/compareReports.test.js b/packages/bundle-size/src/commands/compareReports.test.js deleted file mode 100644 index 3549bbc2d6efb9..00000000000000 --- a/packages/bundle-size/src/commands/compareReports.test.js +++ /dev/null @@ -1,29 +0,0 @@ -const getRemoteReport = jest.fn(); -const collectLocalReport = jest.fn(); -const compareResultsInReports = jest.fn(); -const cliReporter = jest.fn(); - -jest.mock('../reporters/cliReporter', () => cliReporter); -jest.mock('../utils/collectLocalReport', () => collectLocalReport); -jest.mock('../utils/compareResultsInReports', () => ({ compareResultsInReports })); -jest.mock('../utils/getRemoteReport', () => getRemoteReport); - -const sampleReport = require('../../__fixture__/sampleReport'); -const sampleComparedReport = require('../../__fixture__/sampleComparedReport'); -const { handler } = require('./compareReports'); - -describe('compareReports', () => { - it('fetches remote report and compares it with a local data', async () => { - const branchName = 'master'; - - getRemoteReport.mockImplementation(() => ({ commitSHA: 'test', remoteReport: sampleReport })); - collectLocalReport.mockImplementation(() => sampleReport); - compareResultsInReports.mockImplementation(() => sampleComparedReport); - - await handler({ quiet: true, branch: branchName, output: 'cli' }); - - expect(getRemoteReport).toHaveBeenCalledWith(branchName); - expect(compareResultsInReports).toHaveBeenCalledWith(sampleReport, sampleReport); - expect(cliReporter).toHaveBeenCalledWith(sampleComparedReport); - }); -}); diff --git a/packages/bundle-size/src/commands/measure.js b/packages/bundle-size/src/commands/measure.js deleted file mode 100644 index 32633220ebc0f7..00000000000000 --- a/packages/bundle-size/src/commands/measure.js +++ /dev/null @@ -1,68 +0,0 @@ -const chalk = require('chalk'); -const Table = require('cli-table3'); -const fs = require('fs').promises; -const del = require('del'); -const glob = require('glob'); -const path = require('path'); - -const buildFixture = require('../utils/buildFixture'); -const { formatBytes, hrToSeconds } = require('../utils/helpers'); -const prepareFixture = require('../utils/prepareFixture'); - -/** - * @param {typeof import('../index')} options - */ -async function measure(options) { - const { quiet } = options; - - const startTime = process.hrtime(); - const artifactsDir = path.resolve(process.cwd(), 'dist', 'bundle-size'); - - await del(artifactsDir); - - if (!quiet) { - console.log(`${chalk.blue('[i]')} artifacts dir is cleared`); - } - - const fixtures = glob.sync('bundle-size/*.fixture.js', { - cwd: process.cwd(), - }); - - if (!quiet) { - console.log(`${chalk.blue('[i]')} Measuring bundle size for ${fixtures.length} fixture(s)...`); - console.log(fixtures.map(fixture => ` - ${fixture}`).join('\n')); - } - - const preparedFixtures = await Promise.all(fixtures.map(prepareFixture)); - const measurements = await Promise.all(preparedFixtures.map(preparedFixture => buildFixture(preparedFixture, quiet))); - - await fs.writeFile( - path.resolve(process.cwd(), 'dist', 'bundle-size', 'bundle-size.json'), - JSON.stringify(measurements), - ); - - if (!quiet) { - const table = new Table({ - head: ['Fixture', 'Minified size', 'GZIP size'], - }); - const sortedMeasurements = [...measurements].sort((a, b) => a.path.localeCompare(b.path)); - - sortedMeasurements.forEach(r => { - table.push([r.name, formatBytes(r.minifiedSize), formatBytes(r.gzippedSize)]); - }); - - console.log(table.toString()); - console.log(`Completed in ${hrToSeconds(process.hrtime(startTime))}`); - } -} - -// --- - -/** @type {import('yargs').CommandModule} */ -const api = { - command: 'measure', - describe: 'builds bundle size fixtures and generates JSON report', - handler: measure, -}; - -module.exports = api; diff --git a/packages/bundle-size/src/commands/uploadReport.js b/packages/bundle-size/src/commands/uploadReport.js deleted file mode 100644 index eabf3c051dc378..00000000000000 --- a/packages/bundle-size/src/commands/uploadReport.js +++ /dev/null @@ -1,137 +0,0 @@ -const { odata, TableClient, AzureNamedKeyCredential, TableTransaction } = require('@azure/data-tables'); -const chalk = require('chalk'); -const { isCI } = require('ci-info'); - -const collectLocalReport = require('../utils/collectLocalReport'); -const { hrToSeconds } = require('../utils/helpers'); - -const AZURE_STORAGE_ACCOUNT = 'fluentbundlesize'; -const AZURE_STORAGE_TABLE_NAME = 'latest'; -const AZURE_ACCOUNT_KEY = process.env.BUNDLESIZE_ACCOUNT_KEY; - -/** - * @param {import('../utils/collectLocalReport').BundleSizeReportEntry} entry - * @return {string} - */ -function createRowKey(entry) { - // Azure does not support slashes in "rowKey" - // https://docs.microsoft.com/archive/blogs/jmstall/azure-storage-naming-rules - return `${entry.packageName}${entry.path.replace(/\.fixture\.js$/, '').replace(/\//g, '')}`; -} - -/** - * @param {typeof import('../index') & { branch: string, 'commit-sha': string }} options - */ -async function uploadReport(options) { - if (!isCI) { - console.log(`${chalk.red('[e]')} This is command can be executed only in CI`); - process.exit(1); - } - - const { branch, 'commit-sha': commitSHA, quiet } = options; - const startTime = process.hrtime(); - - const localReportStartTime = process.hrtime(); - const localReport = await collectLocalReport(); - - if (!quiet) { - console.log( - [chalk.blue('[i]'), `Local report prepared in ${hrToSeconds(process.hrtime(localReportStartTime))}`].join(' '), - ); - } - - if (typeof AZURE_ACCOUNT_KEY === 'undefined') { - console.log( - [ - chalk.red('[e]'), - 'process.env.BUNDLESIZE_ACCOUNT_KEY is not defined, please verify Azure Pipelines settings', - ].join(' '), - ); - process.exit(1); - } - - const credentials = new AzureNamedKeyCredential(AZURE_STORAGE_ACCOUNT, AZURE_ACCOUNT_KEY); - const client = new TableClient( - `https://${AZURE_STORAGE_ACCOUNT}.table.core.windows.net`, - AZURE_STORAGE_TABLE_NAME, - credentials, - ); - - const transaction = new TableTransaction(); - const transactionStartTime = process.hrtime(); - - const entitiesIterator = await client.listEntities({ - queryOptions: { - filter: odata`PartitionKey eq ${branch}`, - }, - }); - - for await (const entity of entitiesIterator) { - // We can't delete and create entries with the same "rowKey" in the same transaction - // => we delete only entries not present in existing report - const isEntryPresentInExistingReport = Boolean(localReport.find(entry => createRowKey(entry) === entity.rowKey)); - const shouldEntryBeDeleted = isEntryPresentInExistingReport === false; - - if (shouldEntryBeDeleted) { - transaction.deleteEntity(/** @type {string} */ (entity.partitionKey), /** @type {string} */ (entity.rowKey)); - } - } - - localReport.forEach(entry => { - transaction.upsertEntity( - { - partitionKey: branch, - rowKey: createRowKey(entry), - - name: entry.name, - packageName: entry.packageName, - path: entry.path, - - minifiedSize: entry.minifiedSize, - gzippedSize: entry.gzippedSize, - - commitSHA, - }, - 'Replace', - ); - }); - - if (!quiet) { - console.log( - [chalk.blue('[i]'), `A transaction prepared in ${hrToSeconds(process.hrtime(transactionStartTime))}`].join(' '), - ); - } - - const submissionStartTime = process.hrtime(); - await client.submitTransaction(transaction.actions); - - if (!quiet) { - console.log( - [chalk.blue('[i]'), `A transaction submitted in ${hrToSeconds(process.hrtime(submissionStartTime))}`].join(' '), - ); - console.log(`Completed in ${hrToSeconds(process.hrtime(startTime))}`); - } -} - -// --- - -/** @type {import('yargs').CommandModule} */ -const api = { - command: 'upload-report', - describe: 'uploads local results to Azure Table Storage', - builder: { - branch: { - type: 'string', - description: 'A branch to associate a report', - required: true, - }, - 'commit-sha': { - type: 'string', - description: 'Defines a commit sha for a report', - required: true, - }, - }, - handler: uploadReport, -}; - -module.exports = api; diff --git a/packages/bundle-size/src/index.js b/packages/bundle-size/src/index.js deleted file mode 100644 index 75fcc7ec583e51..00000000000000 --- a/packages/bundle-size/src/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const yargs = require('yargs'); - -const cliSetup = yargs - .commandDir('commands', { exclude: /.test\.js$/ }) - .option('quiet', { - alias: 'q', - type: 'boolean', - description: 'Suppress verbose build output', - default: false, - }) - .scriptName('bundle-size') - .version(false).argv; - -module.exports = cliSetup; diff --git a/packages/bundle-size/src/reporters/__snapshots__/markdownReporter.test.js.snap b/packages/bundle-size/src/reporters/__snapshots__/markdownReporter.test.js.snap deleted file mode 100644 index 3af91248a61636..00000000000000 --- a/packages/bundle-size/src/reporters/__snapshots__/markdownReporter.test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`markdownReporter renders a report to a file 1`] = ` -"## 📊 Bundle size report - -| Package & Exports | Baseline (minified/GZIP) | PR | Change | -| :------------------------------------------------------------------------------------- | -----------------------: | ------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| baz-package
An entry with diff | \`0 B\`
\`0 B\` | \`1 kB\`
\`100 B\` | \`1 kB\`
\`100 B\` | -| foo-package
New entry | \`0 B\`
\`0 B\` | \`1 kB\`
\`100 B\` | 🆕 New entry | - -
-Unchanged fixtures - -| Package & Exports | Size (minified/GZIP) | -| ----------------------------------------------------------------------------------------- | -------------------: | -| bar-package
An entry without diff | \`1 kB\`
\`100 B\` | - -
-🤖 This report was generated against commit-hash -" -`; diff --git a/packages/bundle-size/src/reporters/cliReporter.js b/packages/bundle-size/src/reporters/cliReporter.js deleted file mode 100644 index ef5a957a62a005..00000000000000 --- a/packages/bundle-size/src/reporters/cliReporter.js +++ /dev/null @@ -1,72 +0,0 @@ -const chalk = require('chalk'); -const Table = require('cli-table3'); - -const getChangedEntriesInReport = require('../utils/getChangedEntriesInReport'); -const { formatBytes } = require('../utils/helpers'); - -/** - * @param {number} value - * - * @return {string} - */ -function getDirectionSymbol(value) { - if (value < 0) { - return '↓'; - } - - if (value > 0) { - return '↑'; - } - - return ''; -} - -/** - * @param {import('../utils/calculateDiffByMetric').DiffByMetric} diff - * - * @return {string} - */ -function formatDelta({ delta, percent }) { - if (delta === 0) { - return ''; - } - - const colorFn = delta > 0 ? chalk.red : chalk.green; - - return colorFn(percent + getDirectionSymbol(delta)); -} - -/** - * @param {import('../utils/compareResultsInReports').ComparedReport} report - */ -module.exports = async function cliReporter(report) { - const result = new Table({ - colAligns: ['left', 'right', 'right'], - head: ['Fixture', 'Before', 'After (minified/GZIP)'], - }); - const { changedEntries } = getChangedEntriesInReport(report); - - changedEntries.forEach(entry => { - const { diff, gzippedSize, minifiedSize, name, packageName } = entry; - const fixtureColumn = chalk.bold(packageName) + '\n' + name + (diff.empty ? chalk.cyan(' (new)') : ''); - - const minifiedBefore = diff.empty ? 'N/A' : formatBytes(minifiedSize - diff.minified.delta); - const gzippedBefore = diff.empty ? 'N/A' : formatBytes(gzippedSize - diff.gzip.delta); - - const minifiedAfter = formatBytes(minifiedSize); - const gzippedAfter = formatBytes(gzippedSize); - - const beforeColumn = minifiedBefore + '\n' + gzippedBefore; - const afterColumn = - formatDelta(diff.minified) + ' ' + minifiedAfter + '\n' + formatDelta(diff.gzip) + ' ' + gzippedAfter; - - result.push([fixtureColumn, beforeColumn, afterColumn]); - }); - - if (result.length > 0) { - console.log(result.toString()); - return; - } - - console.log(`${chalk.green('[✔]')} No changes found`); -}; diff --git a/packages/bundle-size/src/reporters/cliReporter.test.js b/packages/bundle-size/src/reporters/cliReporter.test.js deleted file mode 100644 index ca0efe3b96f6c7..00000000000000 --- a/packages/bundle-size/src/reporters/cliReporter.test.js +++ /dev/null @@ -1,45 +0,0 @@ -const stripAnsi = require('strip-ansi'); - -const cliReporter = require('./cliReporter'); -const sampleComparedReport = require('../../__fixture__/sampleComparedReport'); - -function noop() { - /* does nothing */ -} - -// We are using "chalk" and "cli-table3" in this reporter, they are adding colors to the output via escape codes that -// makes snapshots look ugly. -// -// It could be disabled for "chalk" but "colors" that is used "cli-table3" is not our dependency. -expect.addSnapshotSerializer({ - test(val) { - return typeof val === 'string'; - }, - print(val) { - /** - * test function makes sure that value is the guarded type - */ - const _val = /** @type {string} */ (val); - - return stripAnsi(_val); - }, -}); - -describe('cliReporter', () => { - it('renders a report to CLI output', async () => { - const log = jest.spyOn(console, 'log').mockImplementation(noop); - await cliReporter(sampleComparedReport); - - expect(log.mock.calls[0][0]).toMatchInlineSnapshot(` - ┌────────────────────┬────────┬───────────────────────┐ - │ Fixture │ Before │ After (minified/GZIP) │ - ├────────────────────┼────────┼───────────────────────┤ - │ baz-package │ 0 B │ 100%↑ 1 kB │ - │ An entry with diff │ 0 B │ 100%↑ 100 B │ - ├────────────────────┼────────┼───────────────────────┤ - │ foo-package │ N/A │ 100%↑ 1 kB │ - │ New entry (new) │ N/A │ 100%↑ 100 B │ - └────────────────────┴────────┴───────────────────────┘ - `); - }); -}); diff --git a/packages/bundle-size/src/reporters/markdownReporter.js b/packages/bundle-size/src/reporters/markdownReporter.js deleted file mode 100644 index 79fba86b9782c8..00000000000000 --- a/packages/bundle-size/src/reporters/markdownReporter.js +++ /dev/null @@ -1,133 +0,0 @@ -const chalk = require('chalk'); -const fs = require('fs').promises; -const path = require('path'); -const prettier = require('prettier'); -const { findPackageRoot } = require('workspace-tools'); - -const getChangedEntriesInReport = require('../utils/getChangedEntriesInReport'); -const { formatBytes } = require('../utils/helpers'); - -const icons = { - increase: 'increase.png', - decrease: 'decrease.png', -}; - -/** - * @param {number} value - * - * @return {string} - */ -function getDirectionSymbol(value) { - /** - * @param {string} iconName - */ - const img = iconName => ``; - - if (value < 0) { - return img(icons.decrease); - } - - if (value > 0) { - return img(icons.increase); - } - - return ''; -} - -/** - * @param {import('../utils/calculateDiffByMetric').DiffByMetric} diff - * - * @return {string} - */ -function formatDelta({ delta }) { - if (delta === 0) { - return ''; - } - - return `\`${formatBytes(delta)}\` ${getDirectionSymbol(delta)}`; -} - -/** - * @param {import('../utils/compareResultsInReports').ComparedReport} result - * @param {string} commitSHA - * @param {boolean} quiet - */ -module.exports = async function markdownReporter(result, commitSHA, quiet) { - const packageRoot = findPackageRoot(__dirname); - - if (!packageRoot) { - throw new Error( - [ - 'Failed to find a package root (directory that contains "package.json" file)', - `Lookup start in: ${__dirname}`, - ].join('\n'), - ); - } - - const artifactsDir = path.resolve(packageRoot, 'dist'); - const artifactsFilename = path.join(artifactsDir, 'bundle-size.md'); - - const report = []; - - report.push('## 📊 Bundle size report'); - report.push(''); - - const { changedEntries, unchangedEntries } = getChangedEntriesInReport(result); - - if (changedEntries.length > 0) { - report.push('| Package & Exports | Baseline (minified/GZIP) | PR | Change |'); - report.push('| :---------------- | -----------------------: | ----: | ---------: |'); - - changedEntries.forEach(entry => { - const title = `${entry.packageName}
${entry.name}`; - const before = entry.diff.empty - ? [`\`${formatBytes(0)}\``, '
', `\`${formatBytes(0)}\``].join('') - : [ - `\`${formatBytes(entry.minifiedSize - entry.diff.minified.delta)}\``, - '
', - `\`${formatBytes(entry.gzippedSize - entry.diff.gzip.delta)}\``, - ].join(''); - const after = [`\`${formatBytes(entry.minifiedSize)}\``, '
', `\`${formatBytes(entry.gzippedSize)}\``].join( - '', - ); - const difference = entry.diff.empty - ? '🆕 New entry' - : [`${formatDelta(entry.diff.minified)}`, '
', `${formatDelta(entry.diff.gzip)}`].join(''); - - report.push(`| ${title} | ${before} | ${after} | ${difference}|`); - }); - - report.push(''); - } - - if (unchangedEntries.length > 0) { - report.push('
'); - report.push('Unchanged fixtures'); - report.push(''); - - report.push('| Package & Exports | Size (minified/GZIP) |'); - report.push('| ----------------- | -------------------: |'); - - unchangedEntries.forEach(entry => { - const title = `${entry.packageName}
${entry.name}`; - const size = [`\`${formatBytes(entry.minifiedSize)}\``, '
', `\`${formatBytes(entry.gzippedSize)}\``].join( - '', - ); - - report.push(`| ${title} | ${size} |`); - }); - - report.push('
'); - } - - report.push( - `🤖 This report was generated against ${commitSHA}`, - ); - - await fs.mkdir(artifactsDir, { recursive: true }); - await fs.writeFile(artifactsFilename, prettier.format(report.join('\n'), { parser: 'markdown' })); - - if (!quiet) { - console.log([chalk.blue('[i]'), `A report file was written to ${artifactsFilename}`].join(' ')); - } -}; diff --git a/packages/bundle-size/src/reporters/markdownReporter.test.js b/packages/bundle-size/src/reporters/markdownReporter.test.js deleted file mode 100644 index b83a1530119632..00000000000000 --- a/packages/bundle-size/src/reporters/markdownReporter.test.js +++ /dev/null @@ -1,13 +0,0 @@ -const markdownReporter = require('./markdownReporter'); -const sampleComparedReport = require('../../__fixture__/sampleComparedReport'); - -describe('markdownReporter', () => { - it('renders a report to a file', async () => { - const writeFile = jest.spyOn(require('fs').promises, 'writeFile').mockImplementation(() => { - return new Promise(resolve => resolve()); - }); - - await markdownReporter(sampleComparedReport, 'commit-hash', true); - expect(writeFile.mock.calls[0][1]).toMatchSnapshot(); - }); -}); diff --git a/packages/bundle-size/src/schema.json b/packages/bundle-size/src/schema.json deleted file mode 100644 index 30b61bdcabbc8c..00000000000000 --- a/packages/bundle-size/src/schema.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "http://json-schema.org/schema", - "$id": "bundle-size-fixture", - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"], - "additionalProperties": false -} diff --git a/packages/bundle-size/src/utils/buildFixture.js b/packages/bundle-size/src/utils/buildFixture.js deleted file mode 100644 index aaaa1ace517f7c..00000000000000 --- a/packages/bundle-size/src/utils/buildFixture.js +++ /dev/null @@ -1,154 +0,0 @@ -const chalk = require('chalk'); -const gzipSize = require('gzip-size'); -const fs = require('fs').promises; -const path = require('path'); -const { minify } = require('terser'); -const webpack = require('webpack'); - -const { hrToSeconds } = require('./helpers'); -const readConfig = require('./readConfig'); - -/** @typedef {import("webpack").Configuration} WebpackConfiguration */ - -/** - * @param {string} fixturePath - * @param {string} outputPath - * - * @return {WebpackConfiguration} - */ -function createWebpackConfig(fixturePath, outputPath) { - return { - name: 'client', - target: 'web', - mode: 'production', - - cache: { - type: 'memory', - }, - externals: { - react: 'React', - 'react-dom': 'ReactDOM', - }, - - entry: fixturePath, - output: { - filename: path.basename(outputPath), - path: path.dirname(outputPath), - - pathinfo: true, - }, - performance: { - hints: false, - }, - optimization: { - minimize: false, - }, - stats: { - optimizationBailout: true, - }, - }; -} - -/** - * @param {WebpackConfiguration} webpackConfig - * @return {Promise} - */ -function webpackAsync(webpackConfig) { - return new Promise((resolve, reject) => { - const compiler = webpack(webpackConfig); - - compiler.run((err, result) => { - if (err) { - reject(err); - } - if (result && result.hasErrors()) { - reject(result.compilation.errors.join('\n')); - } - - resolve(null); - }); - }); -} - -// --- - -/** @typedef {{ name: string, path: string, minifiedSize: number, gzippedSize: number }} BuildResult */ - -/** - * Builds a fixture with Webpack and then minifies it with Terser. Produces two files as artifacts: - * - partially minified file (.output.js) for debugging - * - fully minified file (.min.js) - * - * @param {import('./prepareFixture').PreparedFixture} preparedFixture - * @param {boolean} quiet - * - * @return {Promise} - */ -module.exports = async function buildFixture(preparedFixture, quiet) { - const webpackStartTime = process.hrtime(); - - const config = await readConfig(quiet); - - const webpackOutputPath = preparedFixture.absolutePath.replace(/.fixture.js$/, '.output.js'); - const webpackConfig = createWebpackConfig(preparedFixture.absolutePath, webpackOutputPath); - - await webpackAsync(config.webpack(webpackConfig)); - - if (!quiet) { - console.log( - [ - chalk.blue('[i]'), - `"${path.basename(preparedFixture.relativePath)}": Webpack in ${hrToSeconds(process.hrtime(webpackStartTime))}`, - ].join(' '), - ); - } - - // --- - - const terserStartTime = process.hrtime(); - const terserOutputPath = preparedFixture.absolutePath.replace(/.fixture.js$/, '.min.js'); - - const webpackOutput = await fs.readFile(webpackOutputPath, 'utf8'); - - const [terserOutput, terserOutputMinified] = await Promise.all([ - // Performs only dead-code elimination - /* eslint-disable @typescript-eslint/naming-convention */ - minify(webpackOutput, { - mangle: false, - output: { - beautify: true, - comments: true, - preserve_annotations: true, - }, - }), - minify(webpackOutput, { - output: { - comments: false, - }, - }), - /* eslint-enable @typescript-eslint/naming-convention */ - ]); - - await fs.writeFile(webpackOutputPath, terserOutput.code ?? ''); - await fs.writeFile(terserOutputPath, terserOutputMinified.code ?? ''); - - if (!quiet) { - console.log( - [ - chalk.blue('[i]'), - `"${path.basename(preparedFixture.relativePath)}": Terser in ${hrToSeconds(process.hrtime(terserStartTime))}`, - ].join(' '), - ); - } - - const minifiedSize = (await fs.stat(terserOutputPath)).size; - const gzippedSize = await gzipSize.file(terserOutputPath); - - return { - name: preparedFixture.name, - path: preparedFixture.relativePath, - - minifiedSize, - gzippedSize, - }; -}; diff --git a/packages/bundle-size/src/utils/buildFixture.test.js b/packages/bundle-size/src/utils/buildFixture.test.js deleted file mode 100644 index fcfe7ada8e2ec0..00000000000000 --- a/packages/bundle-size/src/utils/buildFixture.test.js +++ /dev/null @@ -1,51 +0,0 @@ -const fs = require('fs').promises; -const path = require('path'); -const process = require('process'); -const tmp = require('tmp'); - -const buildFixture = require('./buildFixture'); - -/** - * @param {string} fixtureContent - * @return {Promise} - */ -async function setup(fixtureContent) { - const packageDir = tmp.dirSync({ prefix: 'buildFixture', unsafeCleanup: true }); - - const spy = jest.spyOn(process, 'cwd'); - spy.mockReturnValue(packageDir.name); - - const fixtureDir = tmp.dirSync({ dir: packageDir.name, name: 'bundle-size', unsafeCleanup: true }); - const fixture = tmp.fileSync({ dir: fixtureDir.name, name: 'test-fixture.js' }); - - await fs.writeFile(fixture.name, fixtureContent); - - return { - absolutePath: fixture.name, - relativePath: path.relative(packageDir.name, fixture.name), - - name: 'Test fixture', - }; -} - -describe('buildFixture', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('builds fixtures and returns minified & GZIP sizes', async () => { - const fixturePath = await setup(`console.log('Hello world')`); - const buildResult = await buildFixture(fixturePath, true); - - expect(buildResult.name).toBe('Test fixture'); - expect(buildResult.path).toMatch(/bundle-size[\\|/]test-fixture.js/); - - expect(buildResult.minifiedSize).toBeGreaterThan(1); - expect(buildResult.gzippedSize).toBeGreaterThan(1); - }); - - it('should throw on compilation errors', async () => { - const fixturePath = await setup(`import something from 'unknown-pkg'`); - await expect(buildFixture(fixturePath, true)).rejects.toBeDefined(); - }); -}); diff --git a/packages/bundle-size/src/utils/calculateDiffByMetric.js b/packages/bundle-size/src/utils/calculateDiffByMetric.js deleted file mode 100644 index 7607d91128bb20..00000000000000 --- a/packages/bundle-size/src/utils/calculateDiffByMetric.js +++ /dev/null @@ -1,44 +0,0 @@ -const formatter = new Intl.NumberFormat([], { style: 'percent', maximumSignificantDigits: 3 }); - -/** - * @param {number} value - * @param {number} fractionDigits - * - * @return {number} - */ -function roundNumber(value, fractionDigits) { - return Number(value.toFixed(fractionDigits)); -} - -/** - * @param {number} fraction - * - * @return {string} - */ -function formatPercent(fraction) { - if (fraction < 0.001) { - return formatter.format(roundNumber(fraction, 4)); - } - - if (fraction < 0.01) { - return formatter.format(roundNumber(fraction, 3)); - } - - return formatter.format(roundNumber(fraction, 2)); -} - -/** @typedef {{ delta: number, percent: string }} DiffByMetric */ - -/** - * @param {import("../utils/collectLocalReport").BundleSizeReportEntry} local - * @param {import("../utils/collectLocalReport").BundleSizeReportEntry} remote - * @param {'minifiedSize' | 'gzippedSize'} property - * - * @return {DiffByMetric} - */ -module.exports = function calculateDiffByMetric(local, remote, property) { - const delta = local[property] - remote[property]; - const percent = remote[property] === 0 ? 0 : delta / remote[property]; - - return { delta, percent: formatPercent(percent) }; -}; diff --git a/packages/bundle-size/src/utils/calculateDiffByMetric.test.js b/packages/bundle-size/src/utils/calculateDiffByMetric.test.js deleted file mode 100644 index 858914f492ae5a..00000000000000 --- a/packages/bundle-size/src/utils/calculateDiffByMetric.test.js +++ /dev/null @@ -1,59 +0,0 @@ -const calculateDiffByMetric = require('./calculateDiffByMetric'); - -describe('calculateDiffByMetric', () => { - it('calculates difference deltas and percents', () => { - /** @type {import("../utils/collectLocalReport").BundleSizeReportEntry} */ - const remoteEntry = { - packageName: 'test-package', - name: 'Test', - path: 'test.fixture.js', - minifiedSize: 1000, - gzippedSize: 100, - }; - /** @type {import("../utils/collectLocalReport").BundleSizeReportEntry} */ - const localEntry = { - packageName: 'test-package', - name: 'Test', - path: 'test.fixture.js', - minifiedSize: 1500, - gzippedSize: 150, - }; - - expect(calculateDiffByMetric(localEntry, remoteEntry, 'minifiedSize')).toEqual({ - delta: 500, - percent: '50%', - }); - expect(calculateDiffByMetric(localEntry, remoteEntry, 'gzippedSize')).toEqual({ - delta: 50, - percent: '50%', - }); - }); - - it('handles zero values', () => { - /** @type {import("../utils/collectLocalReport").BundleSizeReportEntry} */ - const remoteEntry = { - packageName: 'test-package', - name: 'Test', - path: 'test.fixture.js', - minifiedSize: 0, - gzippedSize: 0, - }; - /** @type {import("../utils/collectLocalReport").BundleSizeReportEntry} */ - const localEntry = { - packageName: 'test-package', - name: 'Test', - path: 'test.fixture.js', - minifiedSize: 0, - gzippedSize: 0, - }; - - expect(calculateDiffByMetric(localEntry, remoteEntry, 'minifiedSize')).toEqual({ - delta: 0, - percent: '0%', - }); - expect(calculateDiffByMetric(localEntry, remoteEntry, 'gzippedSize')).toEqual({ - delta: 0, - percent: '0%', - }); - }); -}); diff --git a/packages/bundle-size/src/utils/collectLocalReport.js b/packages/bundle-size/src/utils/collectLocalReport.js deleted file mode 100644 index 5a319debd51e5a..00000000000000 --- a/packages/bundle-size/src/utils/collectLocalReport.js +++ /dev/null @@ -1,72 +0,0 @@ -const fs = require('fs').promises; -const glob = require('glob'); -const path = require('path'); -const { findGitRoot, findPackageRoot } = require('workspace-tools'); - -/** @typedef {import('../utils/buildFixture').BuildResult} BuildResult */ - -/** @typedef {BuildResult & { packageName: string }} BundleSizeReportEntry */ -/** @typedef {BundleSizeReportEntry[]} BundleSizeReport */ - -/** - * @param {string} reportFile - * - * @return {Promise<{ packageName: string, packageReport: BuildResult[] }>} - */ -async function readReportForPackage(reportFile) { - const reportFilePath = path.resolve(process.cwd(), reportFile); - const packageRoot = findPackageRoot(reportFilePath); - - if (!packageRoot) { - throw new Error( - [ - 'Failed to find a package root (directory that contains "package.json" file)', - `Report file location: ${reportFile}`, - ].join('\n'), - ); - } - - try { - const packageName = path.basename(packageRoot); - const packageReportJSON = await fs.readFile(reportFilePath, 'utf8'); - /** @type {BuildResult[]} */ - const packageReport = JSON.parse(packageReportJSON); - - return { packageName, packageReport }; - } catch (err) { - if (err instanceof SyntaxError) { - throw new Error([`Invalid JSON in "${reportFilePath}"`, err.toString()].join('\n')); - } - if (err instanceof Error) { - throw new Error([`Failed to read JSON from "${reportFilePath}":`, err.toString()].join('\n')); - } - throw new Error(`Unexpected error: ${err}`); - } -} - -const collectLocalReportDefaultOptions = { - root: /** @type {string | undefined} */ (undefined), - reportFilesGlob: 'packages/**/dist/bundle-size/bundle-size.json', -}; -/** - * Collects all reports for packages to a single one. - * - * @param {Partial} options - * @return {Promise} - */ -async function collectLocalReport(options = {}) { - const { reportFilesGlob, root = findGitRoot(process.cwd()) } = { ...collectLocalReportDefaultOptions, ...options }; - const reportFiles = glob.sync(reportFilesGlob, { - cwd: root, - }); - - const reports = await Promise.all(reportFiles.map(readReportForPackage)); - - return reports.reduce((acc, { packageName, packageReport }) => { - const processedReport = packageReport.map(reportEntry => ({ packageName, ...reportEntry })); - - return [...acc, ...processedReport]; - }, /** @type {BundleSizeReport} */ ([])); -} - -module.exports = collectLocalReport; diff --git a/packages/bundle-size/src/utils/collectLocalReport.test.js b/packages/bundle-size/src/utils/collectLocalReport.test.js deleted file mode 100644 index 2b2687f2a20806..00000000000000 --- a/packages/bundle-size/src/utils/collectLocalReport.test.js +++ /dev/null @@ -1,126 +0,0 @@ -const fs = require('fs').promises; -const path = require('path'); -const process = require('process'); -const tmp = require('tmp'); - -// This mock should be not required 😮 -// glob.sync() call in collectLocalReport.js always returns an empty array on Linux/Windows in tests for an unknown -// reason while files are present in filesystem -jest.mock('glob', () => ({ - sync: () => [ - 'packages/package-a/dist/bundle-size/bundle-size.json', - 'packages/package-b/dist/bundle-size/bundle-size.json', - 'packages/components/package-c/dist/bundle-size/bundle-size.json', - ], -})); - -const collectLocalReport = require('./collectLocalReport'); - -function mkPackagesDir() { - const projectDir = tmp.dirSync({ prefix: 'collectLocalReport', unsafeCleanup: true }); - const packagesDir = tmp.dirSync({ dir: projectDir.name, name: 'packages', unsafeCleanup: true }); - - tmp.dirSync({ dir: packagesDir.name, name: 'components', unsafeCleanup: true }); - - const spy = jest.spyOn(process, 'cwd'); - spy.mockReturnValue(projectDir.name); - - return { packagesDir: packagesDir.name, rootDir: projectDir.name }; -} - -/** - * @param {string} packagesDir - * @return {string} - */ -function mkReportDir(packagesDir) { - const distDir = tmp.dirSync({ dir: packagesDir, name: 'dist', unsafeCleanup: true }); - const bundleSizeDir = tmp.dirSync({ dir: distDir.name, name: 'bundle-size', unsafeCleanup: true }); - - tmp.fileSync({ dir: packagesDir, name: 'package.json' }); - - return tmp.fileSync({ dir: bundleSizeDir.name, name: 'bundle-size.json' }).name; -} - -describe('collectLocalReport', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('aggregates all local reports to a single one', async () => { - const { packagesDir, rootDir } = mkPackagesDir(); - - const reportAPath = mkReportDir(tmp.dirSync({ dir: packagesDir, name: 'package-a', unsafeCleanup: true }).name); - const reportBPath = mkReportDir(tmp.dirSync({ dir: packagesDir, name: 'package-b', unsafeCleanup: true }).name); - const reportCPath = mkReportDir( - tmp.dirSync({ dir: path.resolve(packagesDir, 'components'), name: 'package-c', unsafeCleanup: true }).name, - ); - - /** @type {import('../utils/buildFixture').BuildResult[]} */ - const reportA = [ - { name: 'fixtureA1', path: 'path/fixtureA1.js', minifiedSize: 100, gzippedSize: 50 }, - { name: 'fixtureA2', path: 'path/fixtureA2.js', minifiedSize: 200, gzippedSize: 100 }, - ]; - /** @type {import('../utils/buildFixture').BuildResult[]} */ - const reportB = [{ name: 'fixtureB', path: 'path/fixtureB.js', minifiedSize: 10, gzippedSize: 5 }]; - /** @type {import('../utils/buildFixture').BuildResult[]} */ - const reportC = [{ name: 'fixtureC', path: 'path/fixtureC.js', minifiedSize: 4, gzippedSize: 2 }]; - - await fs.writeFile(reportAPath, JSON.stringify(reportA)); - await fs.writeFile(reportBPath, JSON.stringify(reportB)); - await fs.writeFile(reportCPath, JSON.stringify(reportC)); - - expect(await collectLocalReport({ root: rootDir })).toMatchInlineSnapshot(` - Array [ - Object { - "gzippedSize": 50, - "minifiedSize": 100, - "name": "fixtureA1", - "packageName": "package-a", - "path": "path/fixtureA1.js", - }, - Object { - "gzippedSize": 100, - "minifiedSize": 200, - "name": "fixtureA2", - "packageName": "package-a", - "path": "path/fixtureA2.js", - }, - Object { - "gzippedSize": 5, - "minifiedSize": 10, - "name": "fixtureB", - "packageName": "package-b", - "path": "path/fixtureB.js", - }, - Object { - "gzippedSize": 2, - "minifiedSize": 4, - "name": "fixtureC", - "packageName": "package-c", - "path": "path/fixtureC.js", - }, - ] - `); - }); - - it('throws an error if a report file contains invalid JSON', async () => { - const { packagesDir, rootDir } = mkPackagesDir(); - - const reportAPath = mkReportDir(tmp.dirSync({ dir: packagesDir, name: 'package-a', unsafeCleanup: true }).name); - const reportBPath = mkReportDir(tmp.dirSync({ dir: packagesDir, name: 'package-b', unsafeCleanup: true }).name); - const reportCPath = mkReportDir( - tmp.dirSync({ dir: path.resolve(packagesDir, 'components'), name: 'package-c', unsafeCleanup: true }).name, - ); - - /** @type {import('../utils/buildFixture').BuildResult[]} */ - const reportB = [{ name: 'fixtureB', path: 'path/fixtureB.js', minifiedSize: 10, gzippedSize: 5 }]; - /** @type {import('../utils/buildFixture').BuildResult[]} */ - const reportC = [{ name: 'fixtureC', path: 'path/fixtureC.js', minifiedSize: 4, gzippedSize: 2 }]; - - await fs.writeFile(reportAPath, '{ name: "fixture", }'); - await fs.writeFile(reportBPath, JSON.stringify(reportB)); - await fs.writeFile(reportCPath, JSON.stringify(reportC)); - - await expect(collectLocalReport({ root: rootDir })).rejects.toThrow(/Invalid JSON/); - }); -}); diff --git a/packages/bundle-size/src/utils/compareResultsInReports.js b/packages/bundle-size/src/utils/compareResultsInReports.js deleted file mode 100644 index 0eb1f6e2c3b94a..00000000000000 --- a/packages/bundle-size/src/utils/compareResultsInReports.js +++ /dev/null @@ -1,46 +0,0 @@ -const calculateDiffByMetric = require('./calculateDiffByMetric'); - -/** @typedef {import('./calculateDiffByMetric').DiffByMetric} DiffByMetric */ -/** @typedef {{ empty: boolean, minified: DiffByMetric, gzip: DiffByMetric }} DiffForEntry */ - -/** @typedef {import("./collectLocalReport").BundleSizeReportEntry & { diff: DiffForEntry }} ComparedReportEntry */ -/** @typedef {ComparedReportEntry[]} ComparedReport */ - -/** @type {DiffForEntry} */ -const emptyDiff = Object.freeze({ - empty: true, - - minified: { delta: 1, percent: '100%' }, - gzip: { delta: 1, percent: '100%' }, -}); - -/** - * @param {import("./collectLocalReport").BundleSizeReport} localReport - * @param {import("./collectLocalReport").BundleSizeReport} remoteReport - * - * @return {ComparedReport} - */ -function compareResultsInReports(localReport, remoteReport) { - return localReport.map(localEntry => { - const remoteEntry = remoteReport.find( - entry => localEntry.packageName === entry.packageName && localEntry.path === entry.path, - ); - const diff = remoteEntry - ? { - empty: false, - minified: calculateDiffByMetric(localEntry, remoteEntry, 'minifiedSize'), - gzip: calculateDiffByMetric(localEntry, remoteEntry, 'gzippedSize'), - } - : emptyDiff; - - return { - ...localEntry, - diff, - }; - }); -} - -module.exports = { - emptyDiff, - compareResultsInReports, -}; diff --git a/packages/bundle-size/src/utils/compareResultsInReports.test.js b/packages/bundle-size/src/utils/compareResultsInReports.test.js deleted file mode 100644 index 7436f75ce5f8e2..00000000000000 --- a/packages/bundle-size/src/utils/compareResultsInReports.test.js +++ /dev/null @@ -1,85 +0,0 @@ -const { compareResultsInReports } = require('./compareResultsInReports'); - -describe('compareResultsInReports', () => { - it('compares local and remote reports', () => { - /** @type {import("./collectLocalReport").BundleSizeReport} */ - const localReport = [ - { packageName: 'abc', name: 'abc-a', path: 'abc-b.js', minifiedSize: 10, gzippedSize: 5 }, - { packageName: 'abc', name: 'abc-b', path: 'abc-a.js', minifiedSize: 10, gzippedSize: 5 }, - { packageName: 'xyz', name: 'xyz', path: 'xyz.js', minifiedSize: 10, gzippedSize: 5 }, - ]; - /** @type {import("./collectLocalReport").BundleSizeReport} */ - const remoteReport = [ - { packageName: 'abc', name: 'abc-a', path: 'abc-b.js', minifiedSize: 12, gzippedSize: 7 }, - { packageName: 'xyz', name: 'xyz', path: 'xyz.js', minifiedSize: 10, gzippedSize: 5 }, - ]; - - const actual = compareResultsInReports(localReport, remoteReport); - const packageAbcReport = { - fileAbcA: actual[0], - fileAbcB: actual[1], - }; - const packageXyzReport = actual[2]; - - expect(packageAbcReport.fileAbcA).toMatchInlineSnapshot(` - Object { - "diff": Object { - "empty": false, - "gzip": Object { - "delta": -2, - "percent": "-28.6%", - }, - "minified": Object { - "delta": -2, - "percent": "-16.7%", - }, - }, - "gzippedSize": 5, - "minifiedSize": 10, - "name": "abc-a", - "packageName": "abc", - "path": "abc-b.js", - } - `); - expect(packageAbcReport.fileAbcB).toMatchInlineSnapshot(` - Object { - "diff": Object { - "empty": true, - "gzip": Object { - "delta": 1, - "percent": "100%", - }, - "minified": Object { - "delta": 1, - "percent": "100%", - }, - }, - "gzippedSize": 5, - "minifiedSize": 10, - "name": "abc-b", - "packageName": "abc", - "path": "abc-a.js", - } - `); - expect(packageXyzReport).toMatchInlineSnapshot(` - Object { - "diff": Object { - "empty": false, - "gzip": Object { - "delta": 0, - "percent": "0%", - }, - "minified": Object { - "delta": 0, - "percent": "0%", - }, - }, - "gzippedSize": 5, - "minifiedSize": 10, - "name": "xyz", - "packageName": "xyz", - "path": "xyz.js", - } - `); - }); -}); diff --git a/packages/bundle-size/src/utils/getChangedEntriesInReport.js b/packages/bundle-size/src/utils/getChangedEntriesInReport.js deleted file mode 100644 index 7db7cd419a4566..00000000000000 --- a/packages/bundle-size/src/utils/getChangedEntriesInReport.js +++ /dev/null @@ -1,28 +0,0 @@ -/** @typedef {import('../utils/compareResultsInReports').ComparedReport} ComparedReport */ - -const sortComparedReport = require('./sortComparedReport'); - -/** - * @param {ComparedReport} report - * - * @return {{ changedEntries: ComparedReport, unchangedEntries: ComparedReport }} - */ -module.exports = function getChangedEntriesInReport(report) { - const { changedEntries, unchangedEntries } = report.reduce( - (acc, reportEntry) => { - if (reportEntry.diff.gzip.delta === 0 && reportEntry.diff.minified.delta === 0) { - acc.unchangedEntries.push(reportEntry); - return acc; - } - - acc.changedEntries.push(reportEntry); - return acc; - }, - { changedEntries: /** @type {ComparedReport} */ ([]), unchangedEntries: /** @type {ComparedReport} */ ([]) }, - ); - - return { - changedEntries: sortComparedReport(changedEntries), - unchangedEntries: sortComparedReport(unchangedEntries), - }; -}; diff --git a/packages/bundle-size/src/utils/getChangedEntriesInReport.test.js b/packages/bundle-size/src/utils/getChangedEntriesInReport.test.js deleted file mode 100644 index 2cf9deba14219b..00000000000000 --- a/packages/bundle-size/src/utils/getChangedEntriesInReport.test.js +++ /dev/null @@ -1,33 +0,0 @@ -const { emptyDiff } = require('./compareResultsInReports'); -const getChangedEntriesInReport = require('./getChangedEntriesInReport'); - -describe('getChangedEntriesInReport', () => { - it('splits entries to changed an unchanged', () => { - /** @type {import('../utils/compareResultsInReports').ComparedReport} */ - const report = [ - { packageName: 'abc', name: 'abc-a', path: 'abc-a.js', minifiedSize: 0, gzippedSize: 0, diff: emptyDiff }, - { - packageName: 'abc', - name: 'abc-b', - path: 'abc-b.js', - minifiedSize: 0, - gzippedSize: 0, - diff: { - empty: false, - - minified: { delta: 0, percent: '0%' }, - gzip: { delta: 0, percent: '0%' }, - }, - }, - { packageName: 'xyz', name: 'xyz', path: 'xyz.js', minifiedSize: 0, gzippedSize: 0, diff: emptyDiff }, - ]; - const actual = getChangedEntriesInReport(report); - - expect(actual.changedEntries).toHaveLength(2); - expect(actual.changedEntries[0]).toEqual(report[0]); - expect(actual.changedEntries[1]).toEqual(report[2]); - - expect(actual.unchangedEntries).toHaveLength(1); - expect(actual.unchangedEntries[0]).toEqual(report[1]); - }); -}); diff --git a/packages/bundle-size/src/utils/getRemoteReport.js b/packages/bundle-size/src/utils/getRemoteReport.js deleted file mode 100644 index e9fe8fc0c02116..00000000000000 --- a/packages/bundle-size/src/utils/getRemoteReport.js +++ /dev/null @@ -1,46 +0,0 @@ -const chalk = require('chalk'); -const { default: fetch } = require('node-fetch'); - -const MAX_HTTP_ATTEMPT_COUNT = 5; -const REPORT_API_ENDPOINT = `https://fluentbundlesize.azurewebsites.net/api/latest`; - -/** - * Grabs data for a branch from Azure Table Storage. - * - * @param {string} branch - * @param {number} attempt - * - * @return {Promise<{ commitSHA: string, remoteReport: import("../utils/collectLocalReport").BundleSizeReport}>} - */ -module.exports = async function getRemoteReport(branch, attempt = 1) { - try { - const response = await fetch(`${REPORT_API_ENDPOINT}?branch=${branch}`); - /** @type {(import("../utils/collectLocalReport").BundleSizeReportEntry & {commitSHA: string})[]} */ - const result = await response.json(); - - /** @type {import("../utils/collectLocalReport").BundleSizeReport} */ - const remoteReport = result.map(entity => { - const { commitSHA: _, ...rest } = entity; - return rest; - }); - const { commitSHA } = result[result.length - 1]; - - return { commitSHA, remoteReport }; - } catch (err) { - if (err instanceof Error) { - console.log([chalk.yellow('[w]'), err.toString()].join(' ')); - console.log([chalk.yellow('[w]'), 'Failed to fetch report from the remote. Retrying...'].join(' ')); - } - - if (attempt >= MAX_HTTP_ATTEMPT_COUNT) { - console.error( - [chalk.red('[e]'), 'Exceeded 5 attempts to fetch reports, please check previously reported warnings...'].join( - ' ', - ), - ); - process.exit(1); - } - - return getRemoteReport(branch, attempt + 1); - } -}; diff --git a/packages/bundle-size/src/utils/getRemoteReport.test.js b/packages/bundle-size/src/utils/getRemoteReport.test.js deleted file mode 100644 index 6a955874dc1da9..00000000000000 --- a/packages/bundle-size/src/utils/getRemoteReport.test.js +++ /dev/null @@ -1,52 +0,0 @@ -const fetch = jest.fn(); -jest.mock('node-fetch', () => ({ - default: fetch, -})); - -const sampleReport = require('../../__fixture__/sampleReport'); -const getRemoteReport = require('./getRemoteReport'); - -function noop() { - /* does nothing */ -} - -describe('getRemoteReport', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('fetches a remote report', async () => { - /** @type {Partial} */ - const value = { - json: () => { - return Promise.resolve(sampleReport); - }, - }; - fetch.mockImplementation(() => Promise.resolve(value)); - - const { remoteReport } = await getRemoteReport('main'); - - expect(fetch).toHaveBeenCalledTimes(1); - expect(remoteReport).toEqual(sampleReport); - }); - - it('retries to fetch a report', async () => { - /** @type {Partial} */ - const value = { - json: () => { - return Promise.resolve(sampleReport); - }, - }; - fetch - .mockImplementationOnce(() => Promise.reject(new Error('A fetch error'))) - .mockImplementationOnce(() => Promise.reject(new Error('A fetch error'))) - .mockImplementation(() => Promise.resolve(value)); - - jest.spyOn(console, 'log').mockImplementation(noop); - - const { remoteReport } = await getRemoteReport('main'); - - expect(fetch).toHaveBeenCalledTimes(3); - expect(remoteReport).toEqual(sampleReport); - }); -}); diff --git a/packages/bundle-size/src/utils/helpers.js b/packages/bundle-size/src/utils/helpers.js deleted file mode 100644 index 0eb86e6184004f..00000000000000 --- a/packages/bundle-size/src/utils/helpers.js +++ /dev/null @@ -1,25 +0,0 @@ -const prettyBytes = require('pretty-bytes'); - -/** - * @param {number} value - * - * @return {string} - */ -function formatBytes(value) { - return prettyBytes(value, { maximumFractionDigits: 3 }); -} - -/** - * @param {[number, number]} hrtime - * @return {string} - */ -function hrToSeconds(hrtime) { - const raw = hrtime[0] + hrtime[1] / 1e9; - - return raw.toFixed(2) + 's'; -} - -module.exports = { - formatBytes, - hrToSeconds, -}; diff --git a/packages/bundle-size/src/utils/prepareFixture.js b/packages/bundle-size/src/utils/prepareFixture.js deleted file mode 100644 index 97e1f30d80ac20..00000000000000 --- a/packages/bundle-size/src/utils/prepareFixture.js +++ /dev/null @@ -1,93 +0,0 @@ -const { default: Ajv } = require('ajv'); -const Babel = require('@babel/core'); -const fs = require('fs').promises; -const path = require('path'); - -const fixtureSchema = require('../schema.json'); -const ajv = new Ajv(); - -/** @typedef {{ name: string }} FixtureMetadata */ -/** @typedef {{ absolutePath: string, relativePath: string, name: string }} PreparedFixture */ - -/** - * @template T - * @typedef {NonNullable & {[P in keyof T]-?: NonNullable;}} RequiredNonNullableRecord - */ - -/** - * Prepares a fixture file to be compiled with Webpack, grabs data from a default export and removes it. - * - * @param {string} fixture - * - * @return {Promise} - */ -module.exports = async function prepareFixture(fixture) { - const sourceFixturePath = path.resolve(process.cwd(), fixture); - const sourceFixtureCode = await fs.readFile(sourceFixturePath, 'utf8'); - - const result = await Babel.transformAsync(sourceFixtureCode, { - ast: false, - code: true, - - // This instance of Babel should ignore all user's configs and apply only our plugin - configFile: false, // https://babeljs.io/docs/en/options#configfile - babelrc: false, // https://babeljs.io/docs/en/options#babelrc - - plugins: [ - // A Babel plugin that: - // - reads metadata (name, threshold, etc.) - // - removes a default export with metadata - { - visitor: { - // eslint-disable-next-line @typescript-eslint/naming-convention - ExportDefaultDeclaration(exportPath, state) { - const evaluationResult = exportPath.get('declaration').evaluate(); - - if (!evaluationResult.confident) { - // TODO: proper error reporting - throw new Error(); - } - - const valid = ajv.validate(fixtureSchema, evaluationResult.value); - - if (!valid) { - throw new Error(`Validation failed for a schema in a component: ${ajv.errorsText(ajv.errors)}`); - } - - state.file.metadata = evaluationResult.value; - exportPath.remove(); - }, - }, - }, - ], - }); - - /** - * @param {typeof result} value - * @return {value is RequiredNonNullableRecord & {metadata: FixtureMetadata}} - */ - function isTransformedFixtureResultHasMetadata(value) { - return Boolean(value && value.metadata && Object.keys(value.metadata).length); - } - - if (!isTransformedFixtureResultHasMetadata(result)) { - throw new Error( - [ - 'A fixture file should contain a default export with metadata.', - "For example: export default { name: 'Test fixture' }", - ].join('\n'), - ); - } - - const outputFixturePath = path.resolve(process.cwd(), 'dist', fixture); - - await fs.mkdir(path.dirname(outputFixturePath), { recursive: true }); - await fs.writeFile(outputFixturePath, result.code); - - return { - absolutePath: outputFixturePath, - relativePath: fixture, - - name: result.metadata.name, - }; -}; diff --git a/packages/bundle-size/src/utils/prepareFixture.test.js b/packages/bundle-size/src/utils/prepareFixture.test.js deleted file mode 100644 index e9f5609c147e90..00000000000000 --- a/packages/bundle-size/src/utils/prepareFixture.test.js +++ /dev/null @@ -1,63 +0,0 @@ -const fs = require('fs').promises; -const path = require('path'); -const process = require('process'); -const tmp = require('tmp'); - -const prepareFixture = require('./prepareFixture'); - -/** - * @param {string} fixtureContent - * @return {Promise} Returns a relative path to a temporary fixture - */ -async function setup(fixtureContent) { - const packageDir = tmp.dirSync({ prefix: 'prepareFixture', unsafeCleanup: true }); - - const spy = jest.spyOn(process, 'cwd'); - spy.mockReturnValue(packageDir.name); - - const fixtureDir = tmp.dirSync({ dir: packageDir.name, name: 'bundle-size', unsafeCleanup: true }); - const fixture = tmp.fileSync({ dir: fixtureDir.name, name: 'test-fixture.js' }); - - await fs.writeFile(fixture.name, fixtureContent); - - return path.relative(packageDir.name, fixture.name); -} - -describe('prepareFixture', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('reads & removes metadata from a fixture file, writes it to "/dist"', async () => { - const fixturePath = await setup(`import Component from '@fluentui/react-component'; -export default { name: 'Test fixture' } -`); - const fixtureData = await prepareFixture(fixturePath); - - expect(fixtureData.relativePath).toMatch(/bundle-size[\\|/]test-fixture.js/); - expect(fixtureData.name).toBe('Test fixture'); - - expect(await fs.readFile(fixtureData.absolutePath, 'utf8')).toMatchInlineSnapshot( - `"import Component from '@fluentui/react-component';"`, - ); - }); - - it('throws when metadata is not valid', async () => { - const fixturePath = await setup(`import Component from '@fluentui/react-component'; -export default { foo: 'bar' } -`); - - await expect(prepareFixture(fixturePath)).rejects.toMatchInlineSnapshot( - `[Error: unknown file: Validation failed for a schema in a component: data must have required property 'name']`, - ); - }); - - it('throws when metadata is missing', async () => { - const fixturePath = await setup(`import Component from '@fluentui/react-component';`); - - await expect(prepareFixture(fixturePath)).rejects.toMatchInlineSnapshot(` - [Error: A fixture file should contain a default export with metadata. - For example: export default { name: 'Test fixture' }] - `); - }); -}); diff --git a/packages/bundle-size/src/utils/readConfig.js b/packages/bundle-size/src/utils/readConfig.js deleted file mode 100644 index ab49d0f49171ac..00000000000000 --- a/packages/bundle-size/src/utils/readConfig.js +++ /dev/null @@ -1,48 +0,0 @@ -const findUp = require('find-up'); - -/** - * @typedef {{ - * webpack(config: import("webpack").Configuration): import("webpack").Configuration - * }} Config - */ - -/** @type {Config} */ -const defaultConfig = { - webpack: config => config, -}; - -const CONFIG_FILE_NAME = 'bundle-size.config.js'; - -/** @type {Config | undefined} */ -let cache; - -/** - * @returns {Promise} - * - * @param {boolean=} quiet - */ -async function readConfig(quiet) { - // don't use the cache in tests - if (cache && process.env.NODE_ENV !== 'test') { - return cache; - } - - const configPath = await findUp(CONFIG_FILE_NAME, { cwd: process.cwd() }); - - if (!configPath) { - if (!quiet) { - console.log(`no config file found: ${configPath}\n -> fallback to default config`); - } - cache = defaultConfig; - return cache; - } - - if (!quiet) { - console.log(`using config: ${configPath}`); - } - - cache = /** @type {Config}*/ (require(configPath)); - return cache; -} - -module.exports = readConfig; diff --git a/packages/bundle-size/src/utils/readConfig.test.js b/packages/bundle-size/src/utils/readConfig.test.js deleted file mode 100644 index 19872dc095bc70..00000000000000 --- a/packages/bundle-size/src/utils/readConfig.test.js +++ /dev/null @@ -1,68 +0,0 @@ -const fs = require('fs').promises; -const path = require('path'); -const process = require('process'); -const tmp = require('tmp'); - -const readConfig = require('./readConfig'); - -/** - * @param {string} configContent - * @param {number} pwdNesting How many folders to nest the pwd - * @return {Promise} Returns a relative path to a config file - */ -async function setup(configContent, pwdNesting = 0) { - let packageDir = tmp.dirSync({ prefix: 'test-package', unsafeCleanup: true }); - const config = tmp.fileSync({ dir: packageDir.name, name: 'bundle-size.config.js' }); - - for (let i = 0; i < pwdNesting; i++) { - packageDir = tmp.dirSync({ dir: packageDir.name, prefix: 'nested', unsafeCleanup: true }); - } - const spy = jest.spyOn(process, 'cwd'); - spy.mockReturnValue(packageDir.name); - - await fs.writeFile(config.name, configContent); - - return path.relative(packageDir.name, config.name); -} - -describe('prepareFixture', () => { - beforeEach(() => { - jest.resetAllMocks(); - jest.resetModules(); - }); - - it('should read config from package', async () => { - await setup(`module.exports = { webpack: (config) => { config.foo = 'bar'; return config; } }`); - - const config = await readConfig(true); - - expect(config.webpack({})).toEqual({ foo: 'bar' }); - }); - - it('should return default webpack config if no config file defined', async () => { - const config = await readConfig(true); - - expect(config.webpack({})).toEqual({}); - }); - - it('should cache config', async () => { - process.env.NODE_ENV = 'nottest'; - - await setup(`module.exports = { webpack: (config) => config }`); - const firstConfig = await readConfig(true); - await setup(`module.exports = { webpack: (config) => { config.foo = 'bar'; return config; } }`); - const config = await readConfig(true); - - expect(firstConfig).toBe(config); - expect(config.webpack({})).toEqual({}); - - process.env.NODE_ENV = 'test'; - }); - - it.each([1, 2, 3])('should cache config for %i layers of nesting', async nesting => { - await setup(`module.exports = { webpack: (config) => { config.foo = 'bar'; return config; } }`, nesting); - const config = await readConfig(true); - - expect(config.webpack({})).toEqual({ foo: 'bar' }); - }); -}); diff --git a/packages/bundle-size/src/utils/sortComparedReport.js b/packages/bundle-size/src/utils/sortComparedReport.js deleted file mode 100644 index 0c9648f92a03d5..00000000000000 --- a/packages/bundle-size/src/utils/sortComparedReport.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @param {import('../utils/compareResultsInReports').ComparedReportEntry} a - * @param {import('../utils/compareResultsInReports').ComparedReportEntry} b - */ -function compareReports(a, b) { - return a.packageName.localeCompare(b.packageName) || a.path.localeCompare(b.path); -} - -/** - * Sorts entries in a report by "packageName" & "path". - * - * @param {import('../utils/compareResultsInReports').ComparedReport} report - */ -module.exports = function sortComparedReport(report) { - return report.slice().sort(compareReports); -}; diff --git a/packages/bundle-size/src/utils/sortComparedReport.test.js b/packages/bundle-size/src/utils/sortComparedReport.test.js deleted file mode 100644 index d080f398a631bc..00000000000000 --- a/packages/bundle-size/src/utils/sortComparedReport.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const { emptyDiff } = require('./compareResultsInReports'); -const sortComparedReport = require('./sortComparedReport'); - -describe('sortComparedReport', () => { - it('sorts a report by "packageName" & "path', () => { - /** @type {import('../utils/compareResultsInReports').ComparedReport} */ - const report = [ - { packageName: 'bcd', name: 'BCD-B', path: 'bcd-b.js', minifiedSize: 0, gzippedSize: 0, diff: emptyDiff }, - { packageName: 'bcd', name: 'BCD-A', path: 'bcd-a.js', minifiedSize: 0, gzippedSize: 0, diff: emptyDiff }, - { packageName: 'abc', name: 'ABC', path: 'abc.js', minifiedSize: 0, gzippedSize: 0, diff: emptyDiff }, - ]; - const actual = sortComparedReport(report); - - expect(actual[0]).toEqual(report[2]); - expect(actual[1]).toEqual(report[1]); - expect(actual[2]).toEqual(report[0]); - }); -}); diff --git a/packages/bundle-size/tsconfig.json b/packages/bundle-size/tsconfig.json deleted file mode 100644 index 9cbf47ad7f1d5c..00000000000000 --- a/packages/bundle-size/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "noEmit": true, - "resolveJsonModule": true, - "types": ["jest", "node"], - "isolatedModules": true - }, - "include": ["src/**/*.js", "__fixture__/*.js"] -} diff --git a/syncpack.config.js b/syncpack.config.js index e0e8fc2f8915e5..ca62b3434f1775 100644 --- a/syncpack.config.js +++ b/syncpack.config.js @@ -40,9 +40,7 @@ const config = { '@typescript-eslint/experimental-utils', '@typescript-eslint/parser', '@microsoft/load-themed-styles', - 'ajv', 'chalk', - 'cli-table3', 'copy-to-clipboard', 'del', 'eslint-config-airbnb', @@ -56,12 +54,10 @@ const config = { 'eslint-plugin-react-hooks', 'fs-extra', 'glob', - 'gzip-size', 'jju', 'lerna-alias', 'loader-utils', 'lodash', - 'pretty-bytes', 'schema-utils', 'react-is', 'react-hot-loader', @@ -87,10 +83,6 @@ const config = { packages: ['@fluentui/react-conformance'], dependencies: ['chalk'], }, - { - packages: ['@fluentui/bundle-size'], - dependencies: ['@azure/data-tables', 'prettier', 'workspace-tools', 'ci-info', 'node-fetch'], - }, ], }; diff --git a/yarn.lock b/yarn.lock index 9b856af2185be7..48cf7984d9e908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -159,7 +159,7 @@ tslib "^2.2.0" xml2js "^0.4.19" -"@azure/data-tables@13.0.0", "@azure/data-tables@^13.0.0": +"@azure/data-tables@^13.0.0": version "13.0.0" resolved "https://registry.yarnpkg.com/@azure/data-tables/-/data-tables-13.0.0.tgz#e5bf9cce2a32365bb170d1a48a37dd9a7139abda" integrity sha512-OA9X0rsTP7AM+cZENppMljihYWfn02Tkwa1IdbU8jNfCjV5SL8x2e76L7SN5z/sFxAJ3hdcyMa3sAOXvuSs6/Q== @@ -5727,7 +5727,7 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== -"@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.7": +"@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== @@ -9151,7 +9151,7 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -ci-info@3.8.0, ci-info@^3.2.0, ci-info@^3.7.0, ci-info@^3.8.0: +ci-info@3.8.0, ci-info@^3.2.0, ci-info@^3.7.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== @@ -9254,7 +9254,7 @@ cli-spinners@2.6.1, cli-spinners@^2.0.0, cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== -cli-table3@0.6.3, cli-table3@^0.6.1, cli-table3@^0.6.3, cli-table3@~0.6.1: +cli-table3@^0.6.1, cli-table3@^0.6.3, cli-table3@~0.6.1: version "0.6.3" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== @@ -14404,13 +14404,6 @@ gzip-js@^0.3.2: crc32 ">= 0.2.2" deflate-js ">= 0.2.2" -gzip-size@6.0.0, gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - gzip-size@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -14419,6 +14412,13 @@ gzip-size@^5.1.1: duplexer "^0.1.1" pify "^4.0.1" +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + gzip-size@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-7.0.0.tgz#9f9644251f15bc78460fccef4055ae5a5562ac60" @@ -21272,12 +21272,12 @@ prepend-http@^1.0.1: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@2.8.8, "prettier@>=2.2.1 <=2.3.0", prettier@^2.0.1, prettier@^2.2.1, prettier@^2.8.0: +prettier@2.8.8, "prettier@>=2.2.1 <=2.3.0", prettier@^2.0.1, prettier@^2.8.0: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -pretty-bytes@5.6.0, pretty-bytes@^5.6.0: +pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== @@ -24802,7 +24802,7 @@ terser-webpack-plugin@^4.2.3: terser "^5.3.4" webpack-sources "^1.4.3" -terser@5.17.4, terser@^5.16.0, terser@^5.16.8, terser@^5.3.4, terser@^5.5.1: +terser@5.17.4, terser@^5.16.0, terser@^5.16.8, terser@^5.3.4: version "5.17.4" resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.4.tgz#b0c2d94897dfeba43213ed5f90ed117270a2c696" integrity sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw== @@ -26570,7 +26570,7 @@ webpack@4: watchpack "^1.7.4" webpack-sources "^1.4.1" -webpack@5.83.1, "webpack@>=4.0.0 <6.0.0", "webpack@>=4.43.0 <6.0.0", webpack@^5, webpack@^5.1.0, webpack@^5.21.2, webpack@^5.75.0, webpack@^5.9.0: +webpack@5.83.1, "webpack@>=4.0.0 <6.0.0", "webpack@>=4.43.0 <6.0.0", webpack@^5, webpack@^5.1.0, webpack@^5.75.0, webpack@^5.9.0: version "5.83.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.83.1.tgz#fcb69864a0669ac3539a471081952c45b15d1c40" integrity sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA==