Skip to content

Commit

Permalink
test(build): add unit tests for assetFileNamesToFileName function
Browse files Browse the repository at this point in the history
  • Loading branch information
SegaraRai committed Jul 22, 2021
1 parent a5f8e85 commit 91c11ca
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 21 deletions.
156 changes: 156 additions & 0 deletions packages/vite/src/node/__tests__/asset.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { assetFileNamesToFileName, getAssetHash } from '../plugins/asset'

describe('getAssetHash', () => {
test('8-digit hex', () => {
const hash = getAssetHash(Buffer.alloc(0))

expect(hash).toMatch(/^[\da-f]{8}$/)
})
})

describe('assetFileNamesToFileName', () => {
// on Windows, both forward slashes and backslashes may appear in the input
const sourceFilepaths: readonly string[] =
process.platform === 'win32'
? ['C:/path/to/source/input.png', 'C:\\path\\to\\source\\input.png']
: ['/path/to/source/input.png']

for (const sourceFilepath of sourceFilepaths) {
const content = Buffer.alloc(0)
const contentHash = 'abcd1234'

// basic examples

test('a string with no placeholders', () => {
const fileName = assetFileNamesToFileName(
'output.png',
sourceFilepath,
contentHash,
content
)

expect(fileName).toBe('output.png')
})

test('a string with placeholders', () => {
const fileName = assetFileNamesToFileName(
'assets/[name]/[ext]/[extname]/[hash]',
sourceFilepath,
contentHash,
content
)

expect(fileName).toBe('assets/input/png/.png/abcd1234')
})

// function examples

test('a function that uses asset information', () => {
const fileName = assetFileNamesToFileName(
(options) =>
`assets/${options.name.replace(/^C:|[/\\]/g, '')}/${options.type}/${
options.source.length
}`,
sourceFilepath,
contentHash,
content
)

expect(fileName).toBe('assets/pathtosourceinput.png/asset/0')
})

test('a function that returns a string with no placeholders', () => {
const fileName = assetFileNamesToFileName(
() => 'output.png',
sourceFilepath,
contentHash,
content
)

expect(fileName).toBe('output.png')
})

test('a function that returns a string with placeholders', () => {
const fileName = assetFileNamesToFileName(
() => 'assets/[name]/[ext]/[extname]/[hash]',
sourceFilepath,
contentHash,
content
)

expect(fileName).toBe('assets/input/png/.png/abcd1234')
})

// invalid cases

test('a string with an invalid placeholder', () => {
expect(() => {
assetFileNamesToFileName(
'assets/[invalid]',
sourceFilepath,
contentHash,
content
)
}).toThrowError(
'invalid placeholder [invalid] in assetFileNames "assets/[invalid]"'
)

expect(() => {
assetFileNamesToFileName(
'assets/[name][invalid][extname]',
sourceFilepath,
contentHash,
content
)
}).toThrowError(
'invalid placeholder [invalid] in assetFileNames "assets/[name][invalid][extname]"'
)
})

test('a function that returns a string with an invalid placeholder', () => {
expect(() => {
assetFileNamesToFileName(
() => 'assets/[invalid]',
sourceFilepath,
contentHash,
content
)
}).toThrowError(
'invalid placeholder [invalid] in assetFileNames "assets/[invalid]"'
)

expect(() => {
assetFileNamesToFileName(
() => 'assets/[name][invalid][extname]',
sourceFilepath,
contentHash,
content
)
}).toThrowError(
'invalid placeholder [invalid] in assetFileNames "assets/[name][invalid][extname]"'
)
})

test('a number', () => {
expect(() => {
assetFileNamesToFileName(
9876 as unknown as string,
sourceFilepath,
contentHash,
content
)
}).toThrowError('assetFileNames must be a string or a function')
})

test('a function that returns a number', () => {
expect(() => {
assetFileNamesToFileName(
() => 9876 as unknown as string,
sourceFilepath,
contentHash,
content
)
}).toThrowError('assetFileNames must return a string')
})
}
})
55 changes: 34 additions & 21 deletions packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,37 +185,44 @@ export function getAssetFilename(
return assetHashToFilenameMap.get(config)?.get(hash)
}

function assetFileNamesToFileName(
/**
* converts the source filepath of the asset to the output filename based on the assetFileNames option. \
* this function imitates the behavior of rollup.js. \
* https://rollupjs.org/guide/en/#outputassetfilenames
*
* @example
* ```ts
* const content = Buffer.from('text');
* const fileName = assetFileNamesToFileName(
* 'assets/[name].[hash][extname]',
* '/path/to/file.txt',
* getAssetHash(content),
* content
* )
* // fileName: 'assets/file.982d9e3e.txt'
* ```
*
* @param assetFileNames filename pattern. e.g. `'assets/[name].[hash][extname]'`
* @param file filepath of the asset
* @param contentHash hash of the asset. used for `'[hash]'` placeholder
* @param content content of the asset. passed to `assetFileNames` if `assetFileNames` is a function
* @returns output filename
*/
export function assetFileNamesToFileName(
assetFileNames: Exclude<OutputOptions['assetFileNames'], undefined>,
file: string,
contentHash: string,
content: string | Buffer,
config: ResolvedConfig
content: string | Buffer
): string {
const basename = path.basename(file)

// placeholders for `assetFileNames`
// see https://rollupjs.org/guide/en/#outputassetfilenames for available placeholders
// `hash` is slightly different from the rollup's one
const extname = path.extname(basename)
const ext = extname.substr(1)
const name = basename.slice(0, -extname.length)
const hash = contentHash

let assetFileNames: OutputOptions['assetFileNames']
const output = config.build?.rollupOptions?.output
// only the object format is currently considered here
if (output && !Array.isArray(output)) {
assetFileNames = output.assetFileNames
}
// defaults to '<assetsDir>/[name].[hash][extname]'
// slightly different from rollup's one ('assets/[name]-[hash][extname]')
if (assetFileNames == null) {
assetFileNames = path.posix.join(
config.build.assetsDir,
'[name].[hash][extname]'
)
}

if (typeof assetFileNames === 'function') {
assetFileNames = assetFileNames({
name: file,
Expand Down Expand Up @@ -297,11 +304,17 @@ async function fileToBuiltUrl(
const contentHash = getAssetHash(content)
const { search, hash } = parseUrl(id)
const postfix = (search || '') + (hash || '')
const output = config.build?.rollupOptions?.output
const assetFileNames =
(output && !Array.isArray(output) ? output.assetFileNames : undefined) ??
// defaults to '<assetsDir>/[name].[hash][extname]'
// slightly different from rollup's one ('assets/[name]-[hash][extname]')
path.posix.join(config.build.assetsDir, '[name].[hash][extname]')
const fileName = assetFileNamesToFileName(
assetFileNames,
file,
contentHash,
content,
config
content
)
if (!map.has(contentHash)) {
map.set(contentHash, fileName)
Expand Down

0 comments on commit 91c11ca

Please sign in to comment.