Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brotli-bundle-size-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Report extension compressed bundle size using Brotli
24 changes: 16 additions & 8 deletions packages/app/src/cli/services/build/bundle-size.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ import {getBundleSize, formatBundleSize} from './bundle-size.js'
import {describe, expect, test} from 'vitest'
import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import {deflate} from 'node:zlib'
import {brotliCompress, constants as zlibConstants} from 'node:zlib'
import {promisify} from 'node:util'

const deflateAsync = promisify(deflate)
const brotliCompressAsync = promisify(brotliCompress)

async function brotliSize(content: string) {
const compressed = await brotliCompressAsync(Buffer.from(content), {
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]: 11,
},
})
return compressed.byteLength
}

describe('getBundleSize', () => {
test('returns raw and compressed sizes', async () => {
Expand All @@ -20,12 +29,12 @@ describe('getBundleSize', () => {

// Then
expect(result.rawBytes).toBe(10000)
expect(result.compressedBytes).toBe((await deflateAsync(Buffer.from(content))).byteLength)
expect(result.compressedBytes).toBe(await brotliSize(content))
expect(result.compressedBytes).toBeLessThan(result.rawBytes)
})
})

test('compressed size uses deflate to match the backend (Ruby Zlib::Deflate.deflate)', async () => {
test('compressed size uses Brotli to match the backend size gate (Ruby Brotli.deflate)', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const content = JSON.stringify({key: 'value', nested: {array: [1, 2, 3]}})
Expand All @@ -36,8 +45,7 @@ describe('getBundleSize', () => {
const result = await getBundleSize(filePath)

// Then
const expectedCompressed = (await deflateAsync(Buffer.from(content))).byteLength
expect(result.compressedBytes).toBe(expectedCompressed)
expect(result.compressedBytes).toBe(await brotliSize(content))
})
})
})
Expand All @@ -49,7 +57,7 @@ describe('formatBundleSize', () => {
const content = 'x'.repeat(50000)
const filePath = joinPath(tmpDir, 'bundle.js')
await writeFile(filePath, content)
const compressedSize = (await deflateAsync(Buffer.from(content))).byteLength
const compressedSize = await brotliSize(content)

// When
const result = await formatBundleSize(filePath)
Expand All @@ -67,7 +75,7 @@ describe('formatBundleSize', () => {
const content = 'a'.repeat(2 * 1024 * 1024)
const filePath = joinPath(tmpDir, 'bundle.js')
await writeFile(filePath, content)
const compressedSize = (await deflateAsync(Buffer.from(content))).byteLength
const compressedSize = await brotliSize(content)

// When
const result = await formatBundleSize(filePath)
Expand Down
14 changes: 9 additions & 5 deletions packages/app/src/cli/services/build/bundle-size.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import {readFile} from '@shopify/cli-kit/node/fs'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {deflate} from 'node:zlib'
import {brotliCompress, constants as zlibConstants} from 'node:zlib'
import {promisify} from 'node:util'

const deflateAsync = promisify(deflate)
const brotliCompressAsync = promisify(brotliCompress)

/**
* Computes the raw and compressed (deflate) size of a file.
* Uses the same compression algorithm as the Shopify backend (Zlib::Deflate.deflate).
* Computes the raw and compressed (Brotli) size of a file.
* Uses the same compression algorithm as the Shopify backend (Ruby Brotli.deflate).
*/
export async function getBundleSize(filePath: string) {
const content = await readFile(filePath)
const rawBytes = Buffer.byteLength(content)
const compressed = await deflateAsync(Buffer.from(content))
const compressed = await brotliCompressAsync(Buffer.from(content), {
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]: 11,
},
})
const compressedBytes = compressed.byteLength

return {path: filePath, rawBytes, compressedBytes}
Expand Down
Loading