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
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3214,7 +3214,7 @@ async function compileLightningCSS(
const { config } = environment
// replace null byte as lightningcss treats that as a string terminator
// https://github.com/parcel-bundler/lightningcss/issues/874
const filename = removeDirectQuery(id).replace('\0', NULL_BYTE_PLACEHOLDER)
const filename = removeDirectQuery(id).replaceAll('\0', NULL_BYTE_PLACEHOLDER)

let res: LightningCssTransformAttributeResult | LightningCssTransformResult
try {
Expand Down
53 changes: 53 additions & 0 deletions packages/vite/src/shared/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, test } from 'vitest'
import { NULL_BYTE_PLACEHOLDER, VALID_ID_PREFIX } from '../constants'
import { unwrapId, wrapId } from '../utils'

describe('wrapId', () => {
test('wraps an unwrapped id', () => {
expect(wrapId('/foo')).toBe(`${VALID_ID_PREFIX}/foo`)
})

test('is a noop for an already wrapped id', () => {
const wrapped = `${VALID_ID_PREFIX}/foo`
expect(wrapId(wrapped)).toBe(wrapped)
})

test('encodes a leading null byte', () => {
expect(wrapId('\0virtual')).toBe(
`${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}virtual`,
)
})

test('encodes every null byte, not just the first', () => {
// e.g. a commonjs proxy wrapping a virtual module has multiple null bytes,
// and the wrapped id must stay free of raw null bytes to be a valid URL
const id = '\0commonjs-proxy:\0virtual'
const wrapped = wrapId(id)
expect(wrapped).not.toContain('\0')
expect(wrapped).toBe(
`${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}commonjs-proxy:${NULL_BYTE_PLACEHOLDER}virtual`,
)
})
})

describe('unwrapId', () => {
test('unwraps a wrapped id', () => {
expect(unwrapId(`${VALID_ID_PREFIX}/foo`)).toBe('/foo')
})

test('is a noop for an already unwrapped id', () => {
expect(unwrapId('/foo')).toBe('/foo')
})

test('decodes every null byte placeholder, not just the first', () => {
const wrapped = `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}commonjs-proxy:${NULL_BYTE_PLACEHOLDER}virtual`
expect(unwrapId(wrapped)).toBe('\0commonjs-proxy:\0virtual')
})
})

describe('wrapId/unwrapId', () => {
test('round-trips an id with multiple null bytes', () => {
const id = '\0commonjs-proxy:\0virtual'
expect(unwrapId(wrapId(id))).toBe(id)
})
})
4 changes: 2 additions & 2 deletions packages/vite/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ export const isWindows: boolean =
export function wrapId(id: string): string {
return id.startsWith(VALID_ID_PREFIX)
? id
: VALID_ID_PREFIX + id.replace('\0', NULL_BYTE_PLACEHOLDER)
: VALID_ID_PREFIX + id.replaceAll('\0', NULL_BYTE_PLACEHOLDER)
}

/**
* Undo {@link wrapId}'s `/@id/` and null byte replacements.
*/
export function unwrapId(id: string): string {
return id.startsWith(VALID_ID_PREFIX)
? id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, '\0')
? id.slice(VALID_ID_PREFIX.length).replaceAll(NULL_BYTE_PLACEHOLDER, '\0')
: id
}

Expand Down
Loading