Skip to content
Closed
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
6 changes: 6 additions & 0 deletions .changeset/stable-rollup-chunk-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@posthog/plugin-utils': patch
'@posthog/rollup-plugin': patch
---

The default (symbol-set) release mode now derives chunk ids from chunk content instead of a random id per build, so identical builds keep the same chunk id and the same content-hashed `[hash]` file names instead of renaming every chunk on every build. Symbol-set uploads now replace the previous release binding when a stable chunk id is reused by a later release.
6 changes: 6 additions & 0 deletions packages/plugin-utils/src/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ describe('buildSourcemapCliArgs', () => {
expect(args).not.toContain('--delete-after')
})

it('forces symbol-set uploads so stable chunk ids can move to a new release', () => {
const args = buildSourcemapCliArgs(config, { stdin: true }, 'upload')

expect(args).toContain('--force')
})

it.each([
{ releaseMode: 'symbol-set' as const, expected: false },
{ releaseMode: 'event' as const, expected: true },
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-utils/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export function buildSourcemapCliArgs(
// predating the flag.
if (config.sourcemaps.releaseMode === 'event') {
args.push('--release-mode', 'event')
} else if (command === 'upload') {
// Bundler-injected symbol-set ids are content-addressed so identical builds keep stable
// file names. A later release reuses the id but changes the release metadata in the
// upload, so allow the CLI to replace the previous release-bound symbol set.
args.push('--force')
Comment thread
marandaneto marked this conversation as resolved.
}

// On `upload` the caller owns map deletion: `--delete-after` also rewrites
Expand Down
8 changes: 5 additions & 3 deletions packages/rollup-plugin/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ describe('posthogRollupPlugin', () => {
expect(determineChunkIdFromSource(result!.code)).toBe(commentId)
})

it('mints a fresh chunk id per injection', () => {
it('derives the chunk id from content, so identical code keeps its id across rebuilds', () => {
const plugin = testPlugin(options)
const first = plugin.renderChunk.handler(code, { fileName: 'a.js' })
const second = plugin.renderChunk.handler(code, { fileName: 'b.js' })
const rebuilt = testPlugin(options).renderChunk.handler(code, { fileName: 'b.js' })
const other = plugin.renderChunk.handler(`${code}more();`, { fileName: 'c.js' })

expect(determineChunkIdFromSource(first!.code)).not.toBe(determineChunkIdFromSource(second!.code))
expect(determineChunkIdFromSource(rebuilt!.code)).toBe(determineChunkIdFromSource(first!.code))
expect(determineChunkIdFromSource(other!.code)).not.toBe(determineChunkIdFromSource(first!.code))
})

it('does not re-inject already injected code', () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/rollup-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
resolveConfig,
runSourcemapCli,
resolveReleaseId,
createChunkId,
createStableChunkId,
createChunkIdSnippet,
createChunkIdComment,
Expand Down Expand Up @@ -118,7 +117,9 @@ export default function posthogRollupPlugin(userOptions: PostHogRollupPluginOpti
}

if (!eventReleaseMode) {
const chunkId = createChunkId()
// Content-addressed so an unchanged chunk keeps its id — and therefore its
// emitted [hash] file name — across rebuilds.
const chunkId = createStableChunkId(code)
rememberChunkId(chunk.fileName, chunkId)
return injectChunkId(code, chunkId)
}
Expand Down
44 changes: 44 additions & 0 deletions packages/rollup-plugin/test/vite8.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,47 @@ process.stdin.on('end', () => {
'the runtime snippet and upload comment should carry the same chunk id'
)
})

test('keeps [hash] file names identical across identical builds', async (t) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'posthog-rollup-plugin-vite-'))
t.after(() => fs.rm(root, { recursive: true, force: true }))

const cliPath = path.join(root, 'posthog-cli.mjs')
await fs.writeFile(cliPath, '#!/usr/bin/env node\n')
await fs.chmod(cliPath, 0o755)
await fs.writeFile(path.join(root, 'index.html'), '<script type="module" src="/src.ts"></script>')
await fs.writeFile(path.join(root, 'src.ts'), 'console.log("app")')

const buildEntryFiles = async (outDir) => {
await build({
configFile: false,
root,
logLevel: 'silent',
plugins: [
posthogRollupPlugin({
personalApiKey: 'phx_test',
projectId: '1',
cliBinaryPath: cliPath,
sourcemaps: { deleteAfterUpload: false },
}),
],
build: { outDir, minify: 'esbuild' },
})

return (await fs.readdir(path.join(root, outDir), { recursive: true }))
.filter((fileName) => fileName.endsWith('.js'))
.sort()
}

// The injected chunk id is content-addressed, so identical input must emit the same [hash] file
// names — a random id in renderChunk would rename every chunk on every build.
const first = await buildEntryFiles('dist-a')
const second = await buildEntryFiles('dist-b')
assert.ok(first.length > 0, 'the test must discover Vite output nested under assets/')
assert.deepEqual(first, second)

// Changed input still gets a different chunk id and therefore a different hash.
await fs.writeFile(path.join(root, 'src.ts'), 'console.log("app changed")')
const changed = await buildEntryFiles('dist-c')
assert.notDeepEqual(changed, first)
})