Skip to content

Commit

Permalink
Implement new output.hashCharacters option
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Feb 2, 2024
1 parent 723b471 commit b63da96
Show file tree
Hide file tree
Showing 75 changed files with 378 additions and 44 deletions.
1 change: 1 addition & 0 deletions cli/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Basic options:
--generatedCode.objectShorthand Use shorthand properties in generated code
--no-generatedCode.reservedNamesAsProps Always quote reserved names as props
--generatedCode.symbols Use symbols in generated code
--hashCharacters <name> Use the specified character set for file hashes
--no-hoistTransitiveImports Do not hoist transitive imports into entry chunks
--no-indent Don't indent result
--inlineDynamicImports Create single bundle when using dynamic imports
Expand Down
2 changes: 2 additions & 0 deletions docs/command-line-interface/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default {
externalImportAttributes,
footer,
generatedCode,
hashCharacters,
hoistTransitiveImports,
inlineDynamicImports,
interop,
Expand Down Expand Up @@ -398,6 +399,7 @@ Many options have command line equivalents. In those cases, any arguments passed
--generatedCode.objectShorthand Use shorthand properties in generated code
--no-generatedCode.reservedNamesAsProps Always quote reserved names as props
--generatedCode.symbols Use symbols in generated code
--hashCharacters <name> Use the specified character set for file hashes
--no-hoistTransitiveImports Do not hoist transitive imports into entry chunks
--no-indent Don't indent result
--inlineDynamicImports Create single bundle when using dynamic imports
Expand Down
36 changes: 19 additions & 17 deletions docs/configuration-options/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,7 @@ The pattern to use for naming custom emitted assets to include in the build outp
- `[extname]`: The file extension of the asset including a leading dot, e.g. `.css`.
- `[ext]`: The file extension without a leading dot, e.g. `css`.
- `[hash]`: An alias for `[hash64]`. You can also set a specific hash length via e.g. `[hash:10]`.
- `[hash64]`: A hash based on the content of the asset. It uses url-safe base-64 characters `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`. You can also set a specific hash length via e.g. `[hash64:10]`.
- `[hash36]`: The same as `[hash64]` but using only lowercase letters and numbers `abcdefghijklmnopqrstuvwxyz0123456789`. You can also set a specific hash length via e.g. `[hash36:10]`.
- `[hash16]`: The same as `[hash64]` but using hexadecimal encoding `abcdef0123456789`. You can also set a specific hash length via e.g. `[hash16:10]`.
- `[hash]`: A hash based on the content of the asset. You can also set a specific hash length via e.g. `[hash:10]`. By default, it will create a base-64 hash. If you need a reduced character sets, see [`output.hashCharacters`](#output-hashcharacters)
- `[name]`: The file name of the asset excluding any extension.
Forward slashes `/` can be used to place files in sub-directories. When using a function, `assetInfo` is a reduced version of the one in [`generateBundle`](../plugin-development/index.md#generatebundle) without the `fileName`. See also [`output.chunkFileNames`](#output-chunkfilenames), [`output.entryFileNames`](#output-entryfilenames).
Expand Down Expand Up @@ -588,10 +585,7 @@ See also [`output.intro/output.outro`](#output-intro-output-outro).
The pattern to use for naming shared chunks created when code-splitting, or a function that is called per chunk to return such a pattern. Patterns support the following placeholders:
- `[format]`: The rendering format defined in the output options, e.g. `es` or `cjs`.
- `[hash]`: An alias for `[hash64]`. You can also set a specific hash length via e.g. `[hash:10]`.
- `[hash64]`: A hash based only on the content of the final generated chunk, including transformations in [`renderChunk`](../plugin-development/index.md#renderchunk) and any referenced file hashes. It uses url-safe base-64 characters `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`. You can also set a specific hash length via e.g. `[hash64:10]`.
- `[hash36]`: The same as `[hash64]` but using only lowercase letters and numbers `abcdefghijklmnopqrstuvwxyz0123456789`. You can also set a specific hash length via e.g. `[hash36:10]`.
- `[hash16]`: The same as `[hash64]` but using hexadecimal encoding `abcdef0123456789`. You can also set a specific hash length via e.g. `[hash16:10]`.
- `[hash]`: A hash based only on the content of the final generated chunk, including transformations in [`renderChunk`](../plugin-development/index.md#renderchunk) and any referenced file hashes. You can also set a specific hash length via e.g. `[hash:10]`. By default, it will create a base-64 hash. If you need a reduced character sets, see [`output.hashCharacters`](#output-hashcharacters)
- `[name]`: The name of the chunk. This can be explicitly set via the [`output.manualChunks`](#output-manualchunks) option or when the chunk is created by a plugin via [`this.emitFile`](../plugin-development/index.md#this-emitfile). Otherwise, it will be derived from the chunk contents.
Forward slashes `/` can be used to place files in sub-directories. When using a function, `chunkInfo` is a reduced version of the one in [`generateBundle`](../plugin-development/index.md#generatebundle) without properties that depend on file names and no information about the rendered modules as rendering only happens after file names have been generated. You can however access a list of included `moduleIds`. See also [`output.assetFileNames`](#output-assetfilenames), [`output.entryFileNames`](#output-entryfilenames).
Expand Down Expand Up @@ -667,10 +661,7 @@ Promise.resolve()
The pattern to use for chunks created from entry points, or a function that is called per entry chunk to return such a pattern. Patterns support the following placeholders:
- `[format]`: The rendering format defined in the output options, e.g. `es` or `cjs`.
- `[hash]`: An alias for `[hash64]`. You can also set a specific hash length via e.g. `[hash:10]`.
- `[hash64]`: A hash based only on the content of the final generated chunk, including transformations in [`renderChunk`](../plugin-development/index.md#renderchunk) and any referenced file hashes. It uses url-safe base-64 characters `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`. You can also set a specific hash length via e.g. `[hash64:10]`.
- `[hash36]`: The same as `[hash64]` but using only lowercase letters and numbers `abcdefghijklmnopqrstuvwxyz0123456789`. You can also set a specific hash length via e.g. `[hash36:10]`.
- `[hash16]`: The same as `[hash64]` but using hexadecimal encoding `abcdef0123456789`. You can also set a specific hash length via e.g. `[hash16:10]`.
- `[hash]`: A hash based only on the content of the final generated entry chunk, including transformations in [`renderChunk`](../plugin-development/index.md#renderchunk) and any referenced file hashes. You can also set a specific hash length via e.g. `[hash:10]`. By default, it will create a base-64 hash. If you need a reduced character sets, see [`output.hashCharacters`](#output-hashcharacters)
- `[name]`: The file name (without extension) of the entry point, unless the object form of input was used to define a different name.
Forward slashes `/` can be used to place files in sub-directories. When using a function, `chunkInfo` is a reduced version of the one in [`generateBundle`](../plugin-development/index.md#generatebundle) without properties that depend on file names and no information about the rendered modules as rendering only happens after file names have been generated. You can however access a list of included `moduleIds`. See also [`output.assetFileNames`](#output-assetfilenames), [`output.chunkFileNames`](#output-chunkfilenames).
Expand Down Expand Up @@ -872,6 +863,20 @@ const foo = 42;
exports.foo = foo;
```
### output.hashCharacters
| | |
| -------: | :------------------------------ |
| Type: | `"base64" \| "base32" \| "hex"` |
| CLI: | `--hashCharacters <name>` |
| Default: | `"base64"` |
This determines the character set that Rollup is allowed to use in file hashes.
- the default `"base64"` will use url-safe base-64 hashes with potential characters `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`.
- `"base36"` will only use lower-case letters and numbers `abcdefghijklmnopqrstuvwxyz0123456789`.
- `"hex"` will create hexadecimal hashes with characters `abcdef0123456789`.
### output.hoistTransitiveImports
| | |
Expand Down Expand Up @@ -1489,11 +1494,8 @@ The location of the generated bundle. If this is an absolute path, all the `sour
The pattern to use for sourcemaps, or a function that is called per sourcemap to return such a pattern. Patterns support the following placeholders:

- `[format]`: The rendering format defined in the output options, e.g. `es` or `cjs`.
- `[hash]`: An alias for `[hash64]`. You can also set a specific hash length via e.g. `[hash:10]`.
- `[hash64]`: A hash based only on the content of the final generated sourcemap. It uses url-safe base-64 characters `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`. You can also set a specific hash length via e.g. `[hash64:10]`.
- `[hash36]`: The same as `[hash64]` but using only lowercase letters and numbers `abcdefghijklmnopqrstuvwxyz0123456789`. You can also set a specific hash length via e.g. `[hash36:10]`.
- `[hash16]`: The same as `[hash64]` but using hexadecimal encoding `abcdef0123456789`. You can also set a specific hash length via e.g. `[hash16:10]`.
- `[chunkhash]`: The same hash as the one used for the corresponding generated chunk (if any), with the same encoding (`hash64/hash36/hash16`).
- `[hash]`: A hash based only on the content of the final generated sourcemap. You can also set a specific hash length via e.g. `[hash:10]`. By default, it will create a base-64 hash. If you need a reduced character sets, see [`output.hashCharacters`](#output-hashcharacters)
- `[chunkhash]`: The same hash as the one used for the corresponding generated chunk (if any).
- `[name]`: The file name (without extension) of the entry point, unless the object form of input was used to define a different name.

Forward slashes `/` can be used to place files in sub-directories. When using a function, `chunkInfo` is a reduced version of the one in [`generateBundle`](../plugin-development/index.md#generatebundle) without properties that depend on file names and no information about the rendered modules as rendering only happens after file names have been generated. You can however access a list of included `moduleIds`. See also [`output.assetFileNames`](#output-assetfilenames), [`output.chunkFileNames`](#output-chunkfilenames).
Expand Down
1 change: 1 addition & 0 deletions docs/javascript-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const outputOptions = {
externalImportAttributes,
footer,
generatedCode,
hashCharacters,
hoistTransitiveImports,
inlineDynamicImports,
interop,
Expand Down
6 changes: 6 additions & 0 deletions docs/repl/stores/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ export const useOptions = defineStore('options2', () => {
name: 'output.globals',
required: () => true
});
const optionOutputHashCharacters = getSelect({
defaultValue: 'base64',
name: 'output.hashCharacters',
options: () => ['base64', 'base36', 'hex']
});
const optionOutputHoistTransitiveImports = getBoolean({
available: alwaysTrue,
defaultValue: true,
Expand Down Expand Up @@ -432,6 +437,7 @@ export const useOptions = defineStore('options2', () => {
optionOutputGeneratedCodeReservedNamesAsProperties,
optionOutputGeneratedCodeSymbols,
optionOutputGlobals,
optionOutputHashCharacters,
optionOutputHoistTransitiveImports,
optionOutputIndent,
optionOutputInlineDynamicImports,
Expand Down
8 changes: 5 additions & 3 deletions src/Chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import getIndentString from './utils/getIndentString';
import { getNewArray, getOrCreate } from './utils/getOrCreate';
import { getStaticDependencies } from './utils/getStaticDependencies';
import type { HashPlaceholderGenerator } from './utils/hashPlaceholders';
import { replacePlaceholders } from './utils/hashPlaceholders';
import { DEFAULT_HASH_SIZE, replacePlaceholders } from './utils/hashPlaceholders';
import { makeLegal } from './utils/identifierHelpers';
import {
defaultInteropHelpersByInteropType,
Expand Down Expand Up @@ -533,7 +533,8 @@ export default class Chunk {
{
format: () => format,
hash: size =>
hashPlaceholder || (hashPlaceholder = this.getPlaceholder(patternName, size)),
hashPlaceholder ||
(hashPlaceholder = this.getPlaceholder(patternName, size || DEFAULT_HASH_SIZE)),
name: () => this.getChunkName()
}
);
Expand Down Expand Up @@ -566,7 +567,8 @@ export default class Chunk {
chunkhash: () => this.getPreliminaryFileName().hashPlaceholder || '',
format: () => format,
hash: size =>
hashPlaceholder || (hashPlaceholder = this.getPlaceholder(patternName, size)),
hashPlaceholder ||
(hashPlaceholder = this.getPlaceholder(patternName, size || DEFAULT_HASH_SIZE)),
name: () => this.getChunkName()
}
);
Expand Down
4 changes: 4 additions & 0 deletions src/rollup/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ type AddonFunction = (chunk: RenderedChunk) => string | Promise<string>;

type OutputPluginOption = MaybePromise<OutputPlugin | NullValue | false | OutputPluginOption[]>;

type HashCharacters = 'base64' | 'base36' | 'hex';

export interface OutputOptions {
amd?: AmdOptions;
assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string);
Expand All @@ -708,6 +710,7 @@ export interface OutputOptions {
freeze?: boolean;
generatedCode?: GeneratedCodePreset | GeneratedCodeOptions;
globals?: GlobalsOption;
hashCharacters?: HashCharacters;
hoistTransitiveImports?: boolean;
indent?: string | boolean;
inlineDynamicImports?: boolean;
Expand Down Expand Up @@ -758,6 +761,7 @@ export interface NormalizedOutputOptions {
freeze: boolean;
generatedCode: NormalizedGeneratedCodeOptions;
globals: GlobalsOption;
hashCharacters: HashCharacters;
hoistTransitiveImports: boolean;
indent: true | string;
inlineDynamicImports: boolean;
Expand Down
16 changes: 10 additions & 6 deletions src/utils/FileEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import type {
OutputChunk
} from '../rollup/types';
import { BuildPhase } from './buildPhase';
import { getHash64 } from './crypto';
import type { GetHash } from './crypto';
import { getHash64, hasherByType } from './crypto';
import { getOrCreate } from './getOrCreate';
import { defaultHashSize } from './hashPlaceholders';
import { DEFAULT_HASH_SIZE } from './hashPlaceholders';
import { LOGLEVEL_WARN } from './logging';
import {
error,
Expand Down Expand Up @@ -50,7 +51,7 @@ function generateAssetFileName(
{
ext: () => extname(emittedName).slice(1),
extname: () => extname(emittedName),
hash: size => sourceHash.slice(0, Math.max(0, size || defaultHashSize)),
hash: size => sourceHash.slice(0, Math.max(0, size || DEFAULT_HASH_SIZE)),
name: () =>
emittedName.slice(0, Math.max(0, emittedName.length - extname(emittedName).length))
}
Expand Down Expand Up @@ -155,6 +156,7 @@ interface FileEmitterOutput {
bundle: OutputBundleWithPlaceholders;
fileNamesBySource: Map<string, string>;
outputOptions: NormalizedOutputOptions;
getHash: GetHash;
}

export class FileEmitter {
Expand Down Expand Up @@ -254,9 +256,11 @@ export class FileEmitter {
bundle: OutputBundleWithPlaceholders,
outputOptions: NormalizedOutputOptions
): void => {
const getHash = hasherByType[outputOptions.hashCharacters];
const output = (this.output = {
bundle,
fileNamesBySource: new Map<string, string>(),
getHash,
outputOptions
});
for (const emittedFile of this.filesByReferenceId.values()) {
Expand All @@ -270,7 +274,7 @@ export class FileEmitter {
if (consumedFile.fileName) {
this.finalizeAdditionalAsset(consumedFile, consumedFile.source, output);
} else {
const sourceHash = getHash64(consumedFile.source);
const sourceHash = getHash(consumedFile.source);
getOrCreate(consumedAssetsByHash, sourceHash, () => []).push(consumedFile);
}
} else if (consumedFile.type === 'prebuilt-chunk') {
Expand Down Expand Up @@ -439,13 +443,13 @@ export class FileEmitter {
private finalizeAdditionalAsset(
consumedFile: Readonly<ConsumedAsset>,
source: string | Uint8Array,
{ bundle, fileNamesBySource, outputOptions }: FileEmitterOutput
{ bundle, fileNamesBySource, getHash, outputOptions }: FileEmitterOutput
): void {
let { fileName, needsCodeReference, referenceId } = consumedFile;

// Deduplicate assets if an explicit fileName is not provided
if (!fileName) {
const sourceHash = getHash64(source);
const sourceHash = getHash(source);
fileName = fileNamesBySource.get(sourceHash);
if (!fileName) {
fileName = generateAssetFileName(
Expand Down
15 changes: 12 additions & 3 deletions src/utils/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { xxhashBase16, xxhashBase36, xxhashBase64Url } from '../../native';
import type { HashCharacters } from '../rollup/types';

let textEncoder: TextEncoder;

export const getHash64 = (input: string | Uint8Array) => xxhashBase64Url(ensureBuffer(input));
export const getHash36 = (input: string | Uint8Array) => xxhashBase36(ensureBuffer(input));
export const getHash16 = (input: string | Uint8Array) => xxhashBase16(ensureBuffer(input));
export type GetHash = (input: string | Uint8Array) => string;

export const getHash64: GetHash = input => xxhashBase64Url(ensureBuffer(input));
export const getHash36: GetHash = input => xxhashBase36(ensureBuffer(input));
export const getHash16: GetHash = input => xxhashBase16(ensureBuffer(input));

export const hasherByType: Record<HashCharacters, GetHash> = {
base36: getHash36,
base64: getHash64,
hex: getHash16
};

function ensureBuffer(input: string | Uint8Array): Uint8Array {
if (typeof input === 'string') {
Expand Down
14 changes: 7 additions & 7 deletions src/utils/hashPlaceholders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ const hashPlaceholderRight = '}~';
const hashPlaceholderOverhead = hashPlaceholderLeft.length + hashPlaceholderRight.length;

// This is the size of a 128-bits xxhash with base64url encoding
export const maxHashSize = 22;
export const defaultHashSize = 8;
const MAX_HASH_SIZE = 22;
export const DEFAULT_HASH_SIZE = 8;

export type HashPlaceholderGenerator = (optionName: string, hashSize?: number) => string;
export type HashPlaceholderGenerator = (optionName: string, hashSize: number) => string;

export const getHashPlaceholderGenerator = (): HashPlaceholderGenerator => {
let nextIndex = 0;
return (optionName: string, hashSize: number = defaultHashSize) => {
if (hashSize > maxHashSize) {
return (optionName, hashSize) => {
if (hashSize > MAX_HASH_SIZE) {
return error(
logFailedValidation(
`Hashes cannot be longer than ${maxHashSize} characters, received ${hashSize}. Check the "${optionName}" option.`
`Hashes cannot be longer than ${MAX_HASH_SIZE} characters, received ${hashSize}. Check the "${optionName}" option.`
)
);
}
Expand All @@ -40,7 +40,7 @@ export const getHashPlaceholderGenerator = (): HashPlaceholderGenerator => {

const REPLACER_REGEX = new RegExp(
`${hashPlaceholderLeft}[0-9a-zA-Z_$]{1,${
maxHashSize - hashPlaceholderOverhead
MAX_HASH_SIZE - hashPlaceholderOverhead
}}${hashPlaceholderRight}`,
'g'
);
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/mergeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ async function mergeOutputOptions(
)
),
globals: getOption('globals'),
hashCharacters: getOption('hashCharacters'),
hoistTransitiveImports: getOption('hoistTransitiveImports'),
indent: getOption('indent'),
inlineDynamicImports: getOption('inlineDynamicImports'),
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/normalizeOutputOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export async function normalizeOutputOptions(
freeze: config.freeze ?? true,
generatedCode,
globals: config.globals || {},
hashCharacters: config.hashCharacters ?? 'base64',
hoistTransitiveImports: config.hoistTransitiveImports ?? true,
indent: getIndent(config, compact),
inlineDynamicImports,
Expand Down

0 comments on commit b63da96

Please sign in to comment.