diff --git a/apps/test-bundles/just.config.ts b/apps/test-bundles/just.config.ts index be622350d1468..73a68e195f033 100644 --- a/apps/test-bundles/just.config.ts +++ b/apps/test-bundles/just.config.ts @@ -1,14 +1,35 @@ -import { preset, task, resolveCwd } from '@fluentui/scripts-tasks'; +import * as path from 'path'; +import { preset, task, series } from '@fluentui/scripts-tasks'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - parallel-webpack has no types import { run } from 'parallel-webpack'; +import { bundleSizeCollect } from './scripts/bundle-size-collect'; +import { mergeBundleSizes } from './scripts/merge-bundlesizes'; + preset(); // This pacakge doesn't currently have any files that are included in the eslint task // (it only runs on src, not config files) task('code-style', 'prettier'); +task('bundle-size-collect', () => { + const packageName = process.env.PACKAGE ?? ''; + bundleSizeCollect({ packageName }); +}); + task('bundle', done => { - run(resolveCwd('webpack.config.js'), {}, done); + const configPath = path.join(__dirname, 'webpack.config.js'); + const options = {}; + run(configPath, options, done); }); + +task('bundle-size-merge', () => { + const root = path.join(__dirname, 'dist'); + mergeBundleSizes( + [path.join(root, 'react/bundlesize.json'), path.join(root, 'react-northstar/bundlesize.json')], + path.join(root, 'bundlesizes.json'), + ); +}); + +task('bundle-size', series('bundle', 'bundle-size-collect')); diff --git a/apps/test-bundles/package.json b/apps/test-bundles/package.json index 7058fe4196c35..6218993d71f9d 100644 --- a/apps/test-bundles/package.json +++ b/apps/test-bundles/package.json @@ -5,14 +5,13 @@ "private": true, "license": "MIT", "scripts": { - "bundle:size": "just-scripts bundle", + "bundle-size-auditor": "just-scripts bundle-size", "clean": "just-scripts clean", "code-style": "just-scripts code-style", "just": "just-scripts" }, "devDependencies": { "@fluentui/eslint-plugin": "*", - "parallel-webpack": "^2.6.0", "@fluentui/scripts-tasks": "*", "@fluentui/scripts-webpack": "*" } diff --git a/apps/test-bundles/scripts/bundle-size-collect.ts b/apps/test-bundles/scripts/bundle-size-collect.ts new file mode 100644 index 0000000000000..3128e8a100ceb --- /dev/null +++ b/apps/test-bundles/scripts/bundle-size-collect.ts @@ -0,0 +1,40 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * + * collates bundle size information from minified files in apps/test-bundles/dist/ and writes to apps/test-bundles/dist//bundlesizes.json. + * + * It is uploaded as an artifact by the build definition in Azure Dev Ops and used to compare baseline and PR file size information which gets reported by Size Auditor + */ +export function bundleSizeCollect(config: { packageName: string; filename?: string }) { + const { packageName, filename: outputFilename = 'bundlesize.json' } = config; + + if (!packageName) { + throw new Error('🚨 No packageName provided! Set it via env variable name "PACKAGE"'); + } + + const distRoot = path.join(__dirname, '../dist', packageName.replace('@fluentui/', '')); + + const sizes: Record = {}; + + const items = fs.readdirSync(distRoot); + items.forEach(item => { + const file = path.join(distRoot, item); + + const isMinifiedJavascriptFile = item.match(/.min.js$/); + if (isMinifiedJavascriptFile) { + sizes[getComponentName(item)] = getFilesizeInBytes(file); + } + }); + + fs.writeFileSync(path.join(distRoot, outputFilename), JSON.stringify({ sizes })); + + function getFilesizeInBytes(fileName: string) { + return fs.statSync(fileName).size; + } + + function getComponentName(fileName: string) { + return path.basename(fileName, '.min.js'); + } +} diff --git a/apps/test-bundles/scripts/merge-bundlesizes.ts b/apps/test-bundles/scripts/merge-bundlesizes.ts new file mode 100644 index 0000000000000..3c1b484b44f5e --- /dev/null +++ b/apps/test-bundles/scripts/merge-bundlesizes.ts @@ -0,0 +1,14 @@ +import * as fs from 'fs'; + +export function mergeBundleSizes(configPaths: string[], mergeConfigPath: string) { + const result: { sizes: { [entryName: string]: number } } = { + sizes: {}, + }; + + configPaths.forEach(configPath => { + const json = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + Object.assign(result.sizes, json.sizes); + }); + + fs.writeFileSync(mergeConfigPath, JSON.stringify(result)); +} diff --git a/apps/test-bundles/webpack.config.js b/apps/test-bundles/webpack.config.js index c8ad3119a3440..958f7f341c40d 100644 --- a/apps/test-bundles/webpack.config.js +++ b/apps/test-bundles/webpack.config.js @@ -1,3 +1,5 @@ +/* eslint-disable no-shadow */ + // @ts-check const { buildEntries, @@ -8,21 +10,38 @@ const { createEntry, } = require('./webpackUtils'); -const packageName = process.env.PACKAGE; +/** + * + * @param {string} packageName + * @returns + */ +function getEntries(packageName) { + if (packageName === '@fluentui/react-northstar') { + createFluentNorthstarFixtures(); + const entries = buildEntries('@fluentui/react-northstar'); + return entries; + } + + if (packageName === '@fluentui/react') { + createFluentReactFixtures(); + createEntry('@fluentui/keyboard-key'); + + const entries = buildEntries('@fluentui/react'); + entries['keyboard-key'] = buildEntry('@fluentui/keyboard-key'); + return entries; + } + + console.error('🚨 packageName needs to be one of `@fluentui/react` or `@fluentui/react-northstar`!'); + process.exit(1); +} -let entries; -if (packageName === '@fluentui/react-northstar') { - createFluentNorthstarFixtures(); - entries = buildEntries('@fluentui/react-northstar'); -} else if (packageName === '@fluentui/react') { - createFluentReactFixtures(); - createEntry('@fluentui/keyboard-key'); +const packageName = process.env.PACKAGE ?? ''; - entries = buildEntries('@fluentui/react'); - entries['keyboard-key'] = buildEntry('@fluentui/keyboard-key'); -} else { - console.error('🚨 No packageName provided!'); +if (!packageName) { + console.error('🚨 No packageName provided! Set it via env variable name "PACKAGE"'); process.exit(1); } +const entries = getEntries(packageName); +const config = createWebpackConfig(entries, packageName); -module.exports = createWebpackConfig(entries, packageName); +module.exports = config; diff --git a/apps/test-bundles/webpackUtils.js b/apps/test-bundles/webpackUtils.js index f4f997b155309..c76868a36092e 100644 --- a/apps/test-bundles/webpackUtils.js +++ b/apps/test-bundles/webpackUtils.js @@ -7,7 +7,14 @@ const TerserPlugin = require('terser-webpack-plugin'); const FIXTURE_PATH = 'temp/fixtures/'; +/** + * + * @param {{[entryName:string]:{entryPath:string, includeStats:boolean}}} entries + * @param {string} packageName + * @returns + */ function createWebpackConfig(entries, packageName) { + const normalizedPkgName = packageName.replace('@fluentui/', ''); return Object.keys(entries).map(entryName => { /** @type {BundleAnalyzerPlugin.Options} */ let anaylizerPluginOptions = { @@ -53,6 +60,10 @@ function createWebpackConfig(entries, packageName) { entry: { [entryName]: entryPath, }, + output: { + filename: `[name].min.js`, + path: path.resolve(__dirname, 'dist', normalizedPkgName), + }, externals: { react: 'React', 'react-dom': 'ReactDOM', @@ -130,6 +141,9 @@ function createFluentReactFixtures() { }); } +/** + * @param {string} packageName + */ function createEntry(packageName) { try { // import everything from a single package @@ -145,11 +159,13 @@ function createEntry(packageName) { /** * Build webpack entries from created fixtures. * + * @param {string} packageName + * @param {{[entryName:string]:{entryPath:string, includeStats:boolean}}} [entries] * @param {boolean} [includeStats] - Stats are generated and used by the size auditor report - * to check more details on what caused the bundle size change. Due to stats generation being slow, - * and therefore slowing down CI significantly, setting this to true to avoid stats generation. - * If bundle size is changed unexpectedly, developers can drill down deeper on the problem by - * locally running bundle tests. +to check more details on what caused the bundle size change. Due to stats generation being slow, +and therefore slowing down CI significantly, setting this to true to avoid stats generation. +If bundle size is changed unexpectedly, developers can drill down deeper on the problem by +locally running bundle tests. */ function buildEntries(packageName, entries = {}, includeStats = true) { const folderName = getFolderName(packageName); @@ -169,6 +185,8 @@ function buildEntries(packageName, entries = {}, includeStats = true) { /** * Build entries for single fixture with top level import. + * @param {string} packageName + * @param {boolean} [includeStats] */ function buildEntry(packageName, includeStats = true) { const folderName = getFolderName(packageName); @@ -179,6 +197,9 @@ function buildEntry(packageName, includeStats = true) { }; } +/** + * @param {string} packageName + */ function getFolderName(packageName) { return packageName.replace('@fluentui/', ''); } diff --git a/azure-pipelines.bundlesize.yml b/azure-pipelines.bundlesize.yml index 4ca4867581948..bd3a39f59bd16 100644 --- a/azure-pipelines.bundlesize.yml +++ b/azure-pipelines.bundlesize.yml @@ -12,7 +12,7 @@ variables: - template: .devops/templates/variables.yml jobs: - - job: bundlesize + - job: bundle_size workspace: clean: all pool: '1ES-Host-Ubuntu' @@ -53,7 +53,7 @@ jobs: - template: .devops/templates/cleanup.yml - - job: build_react + - job: bundle_size_auditor workspace: clean: all timeoutInMinutes: 75 @@ -66,110 +66,39 @@ jobs: filePath: yarn-ci.sh displayName: yarn - - script: yarn build --to @fluentui/react @fluentui/keyboard-key - displayName: yarn build to @fluentui/react + - script: yarn build --to @fluentui/react @fluentui/keyboard-key @fluentui/react-northstar + displayName: build @fluentui/react, @fluentui/react-northstar - - script: yarn workspace @fluentui/test-bundles bundle:size - displayName: yarn bundle @fluentui/test-bundles + - script: yarn workspace @fluentui/test-bundles bundle-size-auditor + displayName: create @fluentui/react bundle-size entries env: PACKAGE: '@fluentui/react' - - script: yarn bundlesizecollect - displayName: 'Collate Bundle Size Information' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Bundle Size information to Azure Dev Ops Artifacts' - inputs: - PathtoPublish: 'apps/test-bundles/dist/bundlesize.json' - ArtifactName: bundlesize-react - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact dist folder upon build for debug' - inputs: - PathtoPublish: 'apps/test-bundles/dist' - ArtifactName: distdrop-react - - - template: .devops/templates/cleanup.yml - - - job: build_northstar - workspace: - clean: all - timeoutInMinutes: 75 - pool: '1ES-Host-Ubuntu' - steps: - - template: .devops/templates/tools.yml - - - task: Bash@3 - inputs: - filePath: yarn-ci.sh - displayName: yarn - - - script: yarn build --to @fluentui/react-northstar - displayName: yarn build to @fluentui/react-northstar - - - script: yarn workspace @fluentui/test-bundles bundle:size - displayName: yarn bundle @fluentui/test-bundles + - script: yarn workspace @fluentui/test-bundles bundle-size-auditor + displayName: create @fluentui/react-northstar bundle-size entries env: PACKAGE: '@fluentui/react-northstar' - - script: yarn bundlesizecollect - displayName: 'Collate Bundle Size Information' + - script: yarn workspace @fluentui/test-bundles just-scripts bundle-size-merge + displayName: 'Merge @fluentui/react, @fluentui/react-northstar to bundlesizes.json' - task: PublishBuildArtifacts@1 - displayName: 'Publish Bundle Size information to Azure Dev Ops Artifacts' + displayName: 'Publish Merged Bundle Size information' inputs: - PathtoPublish: 'apps/test-bundles/dist/bundlesize.json' - ArtifactName: bundlesize-northstar + PathtoPublish: 'apps/test-bundles/dist/bundlesizes.json' - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact dist folder upon build for debug' inputs: PathtoPublish: 'apps/test-bundles/dist' - ArtifactName: distdrop-northstar + ArtifactName: distdrop - template: .devops/templates/cleanup.yml - - job: merge - pool: '1ES-Host-Ubuntu' - dependsOn: - - build_northstar - - build_react - steps: - - checkout: none - - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact: @fluentui/react' - inputs: - artifactName: 'bundlesize-react' - targetPath: '$(Build.ArtifactStagingDirectory)/react' - - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact: @fluentui/react-northstar' - inputs: - artifactName: 'bundlesize-northstar' - targetPath: '$(Build.ArtifactStagingDirectory)/react-northstar' - - - script: 'sudo apt-get install jq' - displayName: 'Install jq' - - - script: jq -c -s 'reduce .[] as $item ({}; . * $item)' $(Build.ArtifactStagingDirectory)/react-northstar/bundlesize.json $(Build.ArtifactStagingDirectory)/react/bundlesize.json > $(Build.ArtifactStagingDirectory)/bundlesizes.json - displayName: 'Merge @fluentui/react, @fluentui/react-components & @fluentui/react-northstar to bundlesizes.json' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Merged Bundle Size information' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/bundlesizes.json' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact dist folder upon build for debug' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)' - ArtifactName: distdrop - - job: lightrail pool: server dependsOn: - - merge + - bundle_size_auditor steps: - task: odefun.odsp-lightrail-tasks-partner.odsp-lightrail-tasks-SizeAuditorWorker.SizeAuditorWorker@0 displayName: 'Size Auditor Check on LightRail' diff --git a/package.json b/package.json index 64bf873bd30f6..5587f44225d92 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "buildto": "lage build --verbose --to", "buildto:lerna": "node ./scripts/executors/buildTo.js", "bundle": "lage bundle --verbose", - "bundlesizecollect": "node ./scripts/generators/bundle-size-collect", "change": "beachball change --no-commit", "check:change": "beachball check", "check:modified-files": "node -r ./scripts/ts-node/register ./scripts/executors/check-for-modified-files", @@ -287,6 +286,7 @@ "node-polyfill-webpack-plugin": "1.0.2", "nx": "15.6.3", "p-queue": "6.6.2", + "parallel-webpack": "2.6.0", "parse-diff": "0.7.1", "path-browserify": "1.0.1", "plop": "2.6.0", diff --git a/scripts/tasks/src/bundle-size-collect.ts b/scripts/tasks/src/bundle-size-collect.ts deleted file mode 100644 index 75e82c70ba399..0000000000000 --- a/scripts/tasks/src/bundle-size-collect.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -export function bundleSizeCollect() { - // This script collates bundle size information from - // minified files in apps/test-bundles/dist and writes to - // apps/test-bundles/dist/bundlesizes.json. - // It is uploaded as an artifact by the build definition in - // Azure Dev Ops and used to compare baseline and PR file size - // information which gets reported by Size Auditor - - const distRoot = path.join(__dirname, '../../apps/test-bundles/dist'); - const sizes: Record = {}; - const outputFilename = 'bundlesize.json'; - - const items = fs.readdirSync(distRoot); - items.forEach(item => { - const file = path.join(distRoot, item); - - const isMinifiedJavascriptFile = item.match(/.min.js$/); - if (isMinifiedJavascriptFile) { - sizes[getComponentName(item)] = getFilesizeInBytes(file); - } - }); - - fs.writeFileSync(path.join(distRoot, outputFilename), JSON.stringify({ sizes })); - - function getFilesizeInBytes(fileName: string) { - return fs.statSync(fileName).size; - } - - function getComponentName(fileName: string) { - return path.basename(fileName, '.min.js'); - } -} diff --git a/yarn.lock b/yarn.lock index 0eb09dec72c3b..5cfa61cca2a39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20641,7 +20641,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -parallel-webpack@^2.6.0: +parallel-webpack@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/parallel-webpack/-/parallel-webpack-2.6.0.tgz#8a8401adfdeb25ebe9d322cd310801e5ea0c18a9" integrity sha512-aOOLfQ40yWWRt8214F0zNWp0DWbeCs7tJaEur0/XUlYU8Yht1sMTYt+eNrbY4VkM4O/SRSme7cdZJTtIantiOw==