-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #433 from dcastil/other/replace-size-limit-with-ow…
…n-report Replace size-limit action with own metrics-report action
- Loading branch information
Showing
20 changed files
with
1,505 additions
and
367 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
temp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: 'metrics-report-action' | ||
author: 'Dany Castillo' | ||
description: 'Posts a comment with a report about changes in important metrics related to tailwind-merge' | ||
inputs: | ||
github_token: | ||
description: 'Github token of the repository (automatically created by Github)' | ||
default: '${{ github.token }}' | ||
required: false | ||
runs: | ||
using: 'node20' | ||
main: 'src/main.mjs' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "metrics-report-action", | ||
"version": "0.1.0", | ||
"private": true, | ||
"author": "Dany Castillo", | ||
"scripts": { | ||
"start": "node src/main.mjs" | ||
}, | ||
"dependencies": { | ||
"@actions/core": "^1.10.1", | ||
"@actions/exec": "^1.1.1", | ||
"@actions/github": "^6.0.0", | ||
"@octokit/types": "^13.5.0", | ||
"esbuild": "^0.21.5", | ||
"rollup": "^4.18.0" | ||
} | ||
} |
235 changes: 235 additions & 0 deletions
235
.github/actions/metrics-report/src/get-package-size.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// @ts-check | ||
|
||
import fs from 'fs/promises' | ||
import path from 'path' | ||
import { promisify } from 'util' | ||
import zlib from 'zlib' | ||
|
||
import core from '@actions/core' | ||
import { exec } from '@actions/exec' | ||
import { transform } from 'esbuild' | ||
import { rollup } from 'rollup' | ||
|
||
import { actionRootPath, repoRootPath } from './utils/path.mjs' | ||
|
||
/** | ||
* @typedef {object} GetPackageSizeOptions | ||
* @property {boolean=} shouldOmitFailures | ||
*/ | ||
|
||
/** | ||
* @param {GetPackageSizeOptions=} options | ||
*/ | ||
export async function getPackageSize(options = {}) { | ||
await buildPackage() | ||
|
||
return getEntryPointSizes(options) | ||
} | ||
|
||
async function buildPackage() { | ||
core.info('Installing dependencies') | ||
await exec('yarn install --frozen-lockfile', [], { cwd: repoRootPath }) | ||
|
||
core.info('Building package') | ||
await exec('yarn build', [], { cwd: repoRootPath }) | ||
} | ||
|
||
/** | ||
* @typedef {object} EntryPointSize | ||
* @property {BundleSize} bundleSize | ||
* @property {BundleSize[]=} singleExportSizes | ||
*/ | ||
|
||
/** | ||
* @param {GetPackageSizeOptions} param0 | ||
* @returns {Promise<EntryPointSize[]>} | ||
*/ | ||
async function getEntryPointSizes({ shouldOmitFailures }) { | ||
core.info('Getting entry point configs') | ||
const entryPointConfigs = await getEntryPointConfigs() | ||
|
||
core.info('Getting bundle sizes') | ||
|
||
const maybeEntryPointSizes = await Promise.all( | ||
entryPointConfigs.map(async (entryPointConfig, entryPointIndex) => { | ||
const entryPointBundlePath = path.resolve(repoRootPath, entryPointConfig.bundlePath) | ||
|
||
const bundle = await getBundle(entryPointConfig, entryPointBundlePath).catch( | ||
(error) => { | ||
if (shouldOmitFailures) { | ||
core.info( | ||
`Failed to get bundle for ${entryPointConfig.entryPoint}: ${error.message}`, | ||
) | ||
return | ||
} | ||
|
||
throw error | ||
}, | ||
) | ||
|
||
if (!bundle) { | ||
return | ||
} | ||
|
||
const [bundleSize, singleExportSizes] = await Promise.all([ | ||
getBundleSize(entryPointConfig.entryPoint, bundle), | ||
getSingleExportBundleSizes( | ||
entryPointConfig, | ||
entryPointIndex, | ||
entryPointBundlePath, | ||
bundle, | ||
), | ||
]) | ||
|
||
return { | ||
bundleSize, | ||
singleExportSizes, | ||
} | ||
}), | ||
) | ||
|
||
/** @type {any} */ | ||
const entryPointSizes = maybeEntryPointSizes.filter((bundleSize) => bundleSize !== undefined) | ||
|
||
return entryPointSizes | ||
} | ||
|
||
/** | ||
* @typedef {object} EntryPointConfiguration | ||
* @property {string} entryPoint | ||
* @property {string} bundlePath | ||
* @property {'esm' | 'cjs'} format | ||
*/ | ||
|
||
/** | ||
* @returns {Promise<EntryPointConfiguration[]>} | ||
*/ | ||
async function getEntryPointConfigs() { | ||
const pkg = (await import('../../../../package.json', { assert: { type: 'json' } })).default | ||
|
||
return Object.entries(pkg.exports).flatMap(([relativeEntryPointPath, bundleObject]) => { | ||
const entryPointPath = path.join('tailwind-merge', relativeEntryPointPath) | ||
|
||
/** @type {EntryPointConfiguration[]} */ | ||
const entryPointConfigs = [] | ||
|
||
if (bundleObject.import) { | ||
entryPointConfigs.push({ | ||
entryPoint: entryPointPath + ' esm', | ||
bundlePath: bundleObject.import, | ||
format: 'esm', | ||
}) | ||
} | ||
|
||
if (bundleObject.require) { | ||
entryPointConfigs.push({ | ||
entryPoint: entryPointPath + ' cjs', | ||
bundlePath: bundleObject.require, | ||
format: 'cjs', | ||
}) | ||
} | ||
|
||
return entryPointConfigs | ||
}) | ||
} | ||
|
||
/** | ||
* @param {EntryPointConfiguration} entryPointConfig | ||
* @param {string} entryPoint | ||
*/ | ||
async function getBundle(entryPointConfig, entryPoint) { | ||
const rollupBuild = await rollup({ input: entryPoint }) | ||
let rollupOutput | ||
|
||
try { | ||
rollupOutput = await rollupBuild.generate({ | ||
format: entryPointConfig.format, | ||
}) | ||
} catch (error) { | ||
await rollupBuild.close() | ||
throw error | ||
} | ||
|
||
await rollupBuild.close() | ||
|
||
if (rollupOutput.output.length !== 1) { | ||
throw Error(`Expected a single output chunk for bundle ${entryPoint}`) | ||
} | ||
|
||
const outputChunk = rollupOutput.output[0] | ||
|
||
return outputChunk | ||
} | ||
|
||
/** | ||
* @param {EntryPointConfiguration} entryPointConfig | ||
* @param {number} entryPointIndex | ||
* @param {string} bundlePath | ||
* @param {import('rollup').OutputChunk} bundle | ||
*/ | ||
async function getSingleExportBundleSizes(entryPointConfig, entryPointIndex, bundlePath, bundle) { | ||
if (entryPointConfig.format === 'esm' && bundle.exports.length !== 0) { | ||
const singleExportBundlesDirPath = path.resolve( | ||
actionRootPath, | ||
`temp/bundle-${entryPointIndex}`, | ||
) | ||
|
||
await fs.mkdir(singleExportBundlesDirPath, { recursive: true }) | ||
|
||
return Promise.all( | ||
bundle.exports.map(async (exportName) => { | ||
const entryPoint = await createEntryPoint( | ||
singleExportBundlesDirPath, | ||
bundlePath, | ||
exportName, | ||
) | ||
|
||
const singleExportBundle = await getBundle(entryPointConfig, entryPoint) | ||
|
||
return getBundleSize(exportName, singleExportBundle) | ||
}), | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} singleExportBundlesDirPath | ||
* @param {string} bundlePath | ||
* @param {string} exportName | ||
*/ | ||
async function createEntryPoint(singleExportBundlesDirPath, bundlePath, exportName) { | ||
const filePath = path.resolve(singleExportBundlesDirPath, `${exportName}.mjs`) | ||
const fileContent = `export { ${exportName} } from '${bundlePath}'` | ||
|
||
await fs.writeFile(filePath, fileContent) | ||
|
||
return filePath | ||
} | ||
|
||
/** | ||
* @typedef {object} BundleSize | ||
* @property {string} label | ||
* @property {number} size | ||
* @property {number} sizeMinified | ||
* @property {number} sizeBrotliCompressed | ||
*/ | ||
|
||
/** | ||
* @param {string} label | ||
* @param {import('rollup').OutputChunk} bundle | ||
* @returns {Promise<BundleSize>} | ||
*/ | ||
async function getBundleSize(label, bundle) { | ||
const esBuildTransformResult = await transform(bundle.code, { minify: true }) | ||
const minifiedCode = esBuildTransformResult.code | ||
const brotliCompressedCode = (await brotliCompress(minifiedCode)).toString() | ||
|
||
return { | ||
label, | ||
size: bundle.code.length, | ||
sizeMinified: minifiedCode.length, | ||
sizeBrotliCompressed: brotliCompressedCode.length, | ||
} | ||
} | ||
|
||
const brotliCompress = promisify(zlib.brotliCompress) |
Oops, something went wrong.