Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2585 - Feature adds ability to use a function that returns a pattern string in all places where you could use a pattern string befor. #3658

Merged
merged 8 commits into from Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/05-plugin-development.md
Expand Up @@ -236,12 +236,12 @@ Output generation hooks can provide information about a generated bundle and mod
The first hook of the output generation phase is [outputOptions](guide/en/#outputoptions), the last one is either [generateBundle](guide/en/#generatebundle) if the output was successfully generated via `bundle.generate(...)`, [writeBundle](guide/en/#writebundle) if the output was successfully generated via `bundle.write(...)`, or [renderError](guide/en/#rendererror) if an error occurred at any time during the output generation.

#### `augmentChunkHash`
Type: `(preRenderedChunk: PreRenderedChunk) => string`<br>
Type: `(chunkInfo: ChunkInfo) => string`<br>
Kind: `sync, sequential`<br>
Previous Hook: [`renderDynamicImport`](guide/en/#renderdynamicimport) for each dynamic import expression.<br>
Next Hook: [`resolveFileUrl`](guide/en/#resolvefileurl) for each use of `import.meta.ROLLUP_FILE_URL_referenceId` and [`resolveImportMeta`](guide/en/#resolveimportmeta) for all other accesses to `import.meta`.

Can be used to augment the hash of individual chunks. Called for each Rollup output chunk. Returning a falsy value will not modify the hash. Truthy values will be passed to [`hash.update`](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html#crypto_hash_update_data_inputencoding).
Can be used to augment the hash of individual chunks. Called for each Rollup output chunk. Returning a falsy value will not modify the hash. Truthy values will be passed to [`hash.update`](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html#crypto_hash_update_data_inputencoding). The `chunkInfo` is a reduced version of the one in [`generateBundle`](guide/en/#generatebundle) without properties that rely on file names.

The following plugin will invalidate the hash of chunk `foo` with the timestamp of the last build:

Expand Down Expand Up @@ -282,6 +282,7 @@ Called at the end of `bundle.generate()` or immediately before the files are wri
// AssetInfo
{
fileName: string,
name?: string,
source: string | Uint8Array,
type: 'asset',
}
Expand Down
24 changes: 15 additions & 9 deletions docs/999-big-list-of-options.md
Expand Up @@ -359,17 +359,19 @@ export default {


#### output.assetFileNames
Type: `string`<br>
Type: `string | ((assetInfo: AssetInfo) => string)`<br>
CLI: `--assetFileNames <pattern>`<br>
Default: `"assets/[name]-[hash][extname]"`

The pattern to use for naming custom emitted assets to include in the build output. Pattern supports the following placeholders:
The pattern to use for naming custom emitted assets to include in the build output, or a function that is called per asset to return such a pattern. Patterns support the following placeholders:
* `[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]`: A hash based on the name and content of the asset.
* `[name]`: The file name of the asset excluding any extension.

Forward slashes `/` can be used to place files in sub-directories. See also [`output.chunkFileNames`](guide/en/#outputchunkfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).
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`](guide/en/#generatebundle) without the `fileName`. See also [`output.chunkFileNames`](guide/en/#outputchunkfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).

You can also supply a function that returns a pattern as string.

#### output.banner/output.footer
Type: `string | (() => string | Promise<string>)`<br>
Expand All @@ -392,16 +394,18 @@ export default {
See also [`output.intro/output.outro`](guide/en/#outputintrooutputoutro).

#### output.chunkFileNames
Type: `string`<br>
Type: `string | ((chunkInfo: ChunkInfo) => string)`<br>
CLI: `--chunkFileNames <pattern>`<br>
Default: `"[name]-[hash].js"`

The pattern to use for naming shared chunks created when code-splitting. Pattern supports the following placeholders:
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]`: A hash based on the content of the chunk and the content of all its dependencies.
* `[name]`: The name of the chunk. This can be explicitly set via the [`output.manualChunks`](guide/en/#outputmanualchunks) option or when the chunk is created by a plugin via [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string). Otherwise it will be derived from the chunk contents.

Forward slashes `/` can be used to place files in sub-directories. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).
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`](guide/en/#generatebundle) without properties that depend on file names. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).

You can also supply a function that returns a pattern as string.

#### output.compact
Type: `boolean`<br>
Expand All @@ -411,23 +415,25 @@ Default: `false`
This will minify the wrapper code generated by rollup. Note that this does not affect code written by the user. This option is useful when bundling pre-minified code.

#### output.entryFileNames
Type: `string`<br>
Type: `string | ((chunkInfo: ChunkInfo) => string)`<br>
CLI: `--entryFileNames <pattern>`<br>
Default: `"[name].js"`

The pattern to use for chunks created from entry points. Pattern supports the following placeholders:
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]`: A hash based on the content of the entry point and the content of all its dependencies.
* `[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. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.chunkFileNames`](guide/en/#outputchunkfilenames).
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`](guide/en/#generatebundle) without properties that depend on file names. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.chunkFileNames`](guide/en/#outputchunkfilenames).

This pattern will also be used when using the [`output.preserveModules`](guide/en/#outputpreservemodules) option. Here there is a different set of placeholders available, though:
* `[format]`: The rendering format defined in the output options.
* `[name]`: The file name (without extension) of the file.
* `[ext]`: The extension of the file.
* `[extname]`: The extension of the file, prefixed by `.` if it is not empty.

You can also supply a function that returns a pattern as string.

#### output.extend
Type: `boolean`<br>
CLI: `--extend`/`--no-extend`<br>
Expand Down
2 changes: 1 addition & 1 deletion src/Bundle.ts
Expand Up @@ -84,7 +84,7 @@ export default class Bundle {
for (const chunk of chunks) {
const chunkDescription = (outputBundle[
chunk.id!
] = chunk.getPrerenderedChunk() as OutputChunk);
] = chunk.getChunkInfoWithFileNames() as OutputChunk);
chunkDescription.fileName = chunk.id!;
}
await Promise.all(
Expand Down
89 changes: 52 additions & 37 deletions src/Chunk.ts
Expand Up @@ -387,19 +387,24 @@ export default class Chunk {
? [options.entryFileNames, 'output.entryFileNames']
: [options.chunkFileNames, 'output.chunkFileNames'];
return makeUnique(
renderNamePattern(pattern, patternName, {
format: () => options.format,
hash: () =>
includeHash
? this.computeContentHashWithDependencies(
addons,
options,
existingNames,
outputPluginDriver
)
: '[hash]',
name: () => this.getChunkName()
}),
renderNamePattern(
pattern,
patternName,
{
format: () => options.format,
hash: () =>
includeHash
? this.computeContentHashWithDependencies(
addons,
options,
existingNames,
outputPluginDriver
)
: '[hash]',
name: () => this.getChunkName()
},
this.getChunkInfo.bind(this)
),
existingNames
);
}
Expand All @@ -422,44 +427,33 @@ export default class Chunk {
: options.entryFileNames;
path = relative(
preserveModulesRelativeDir,
`${dirname(sanitizedId)}/${renderNamePattern(pattern, 'output.entryFileNames', {
ext: () => extension.substr(1),
extname: () => extension,
format: () => options.format as string,
name: () => this.getChunkName()
})}`
`${dirname(sanitizedId)}/${renderNamePattern(
pattern,
'output.entryFileNames',
{
ext: () => extension.substr(1),
extname: () => extension,
format: () => options.format as string,
name: () => this.getChunkName()
},
this.getChunkInfo.bind(this)
)}`
);
} else {
path = `_virtual/${basename(sanitizedId)}`;
}
return makeUnique(normalize(path), existingNames);
}

getChunkName(): string {
return this.name || (this.name = sanitizeFileName(this.getFallbackChunkName()));
}

getExportNames(): string[] {
return (
this.sortedExportNames || (this.sortedExportNames = Object.keys(this.exportsByName!).sort())
);
}

getPrerenderedChunk(): PreRenderedChunk {
getChunkInfo(): PreRenderedChunk {
const facadeModule = this.facadeModule;
const getChunkName = this.getChunkName.bind(this);
return {
code: undefined,
dynamicImports: Array.from(this.dynamicDependencies, getId),
exports: this.getExportNames(),
facadeModuleId: facadeModule && facadeModule.id,
fileName: undefined,
implicitlyLoadedBefore: Array.from(this.implicitlyLoadedBefore, getId),
imports: Array.from(this.dependencies, getId),
isDynamicEntry: this.dynamicEntryModules.length > 0,
isEntry: facadeModule !== null && facadeModule.isEntryPoint,
isImplicitEntry: this.implicitEntryModules.length > 0,
map: undefined,
modules: this.renderedModules!,
get name() {
return getChunkName();
Expand All @@ -468,13 +462,34 @@ export default class Chunk {
};
}

getChunkInfoWithFileNames(): RenderedChunk {
return Object.assign(this.getChunkInfo(), {
code: undefined,
dynamicImports: Array.from(this.dynamicDependencies, getId),
fileName: this.id!,
implicitlyLoadedBefore: Array.from(this.implicitlyLoadedBefore, getId),
imports: Array.from(this.dependencies, getId),
map: undefined
});
}

getChunkName(): string {
return this.name || (this.name = sanitizeFileName(this.getFallbackChunkName()));
}

getExportNames(): string[] {
return (
this.sortedExportNames || (this.sortedExportNames = Object.keys(this.exportsByName!).sort())
);
}

getRenderedHash(outputPluginDriver: PluginDriver): string {
if (this.renderedHash) return this.renderedHash;
const hash = createHash();
const hashAugmentation = outputPluginDriver.hookReduceValueSync(
'augmentChunkHash',
'',
[this.getPrerenderedChunk()],
[this.getChunkInfo()],
(hashAugmentation, pluginHash) => {
if (pluginHash) {
hashAugmentation += pluginHash;
Expand Down
35 changes: 18 additions & 17 deletions src/rollup/types.d.ts
Expand Up @@ -545,15 +545,15 @@ export interface OutputOptions {
define?: string;
id?: string;
};
assetFileNames?: string;
assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string);
banner?: string | (() => string | Promise<string>);
chunkFileNames?: string;
chunkFileNames?: string | ((chunkInfo: PreRenderedChunk) => string);
compact?: boolean;
// only required for bundle.write
dir?: string;
/** @deprecated Use the "renderDynamicImport" plugin hook instead. */
dynamicImportFunction?: string;
entryFileNames?: string;
entryFileNames?: string | ((chunkInfo: PreRenderedChunk) => string);
esModule?: boolean;
exports?: 'default' | 'named' | 'none' | 'auto';
extend?: boolean;
Expand Down Expand Up @@ -592,14 +592,14 @@ export interface NormalizedOutputOptions {
define: string;
id?: string;
};
assetFileNames: string;
assetFileNames: string | ((chunkInfo: PreRenderedAsset) => string);
banner: () => string | Promise<string>;
chunkFileNames: string;
chunkFileNames: string | ((chunkInfo: PreRenderedChunk) => string);
compact: boolean;
dir: string | undefined;
/** @deprecated Use the "renderDynamicImport" plugin hook instead. */
dynamicImportFunction: string | undefined;
entryFileNames: string;
entryFileNames: string | ((chunkInfo: PreRenderedChunk) => string);
esModule: boolean;
exports: 'default' | 'named' | 'none' | 'auto';
extend: boolean;
Expand Down Expand Up @@ -642,12 +642,16 @@ export interface SerializedTimings {
[label: string]: [number, number, number];
}

export interface OutputAsset {
export interface PreRenderedAsset {
name: string | undefined;
source: string | Uint8Array;
type: 'asset';
}

export interface OutputAsset extends PreRenderedAsset {
fileName: string;
/** @deprecated Accessing "isAsset" on files in the bundle is deprecated, please use "type === \'asset\'" instead */
isAsset: true;
source: string | Uint8Array;
type: 'asset';
}

export interface RenderedModule {
Expand All @@ -658,17 +662,11 @@ export interface RenderedModule {
}

export interface PreRenderedChunk {
code?: string;
dynamicImports: string[];
exports: string[];
facadeModuleId: string | null;
fileName?: string;
implicitlyLoadedBefore: string[];
imports: string[];
isDynamicEntry: boolean;
isEntry: boolean;
isImplicitEntry: boolean;
map?: SourceMap;
modules: {
[id: string]: RenderedModule;
};
Expand All @@ -677,13 +675,16 @@ export interface PreRenderedChunk {
}

export interface RenderedChunk extends PreRenderedChunk {
code?: string;
dynamicImports: string[];
fileName: string;
implicitlyLoadedBefore: string[];
imports: string[];
map?: SourceMap;
}

export interface OutputChunk extends RenderedChunk {
code: string;
map?: SourceMap;
type: 'chunk';
}

export interface SerializablePluginCache {
Expand Down
33 changes: 20 additions & 13 deletions src/utils/FileEmitter.ts
Expand Up @@ -6,6 +6,7 @@ import {
FilePlaceholder,
NormalizedInputOptions,
OutputBundleWithPlaceholders,
PreRenderedAsset,
WarningHandler
} from '../rollup/types';
import { BuildPhase } from './buildPhase';
Expand All @@ -28,7 +29,7 @@ import { isPlainPathFragment } from './relativeId';
import { makeUnique, renderNamePattern } from './renderNamePattern';

interface OutputSpecificFileData {
assetFileNames: string;
assetFileNames: string | ((assetInfo: PreRenderedAsset) => string);
bundle: OutputBundleWithPlaceholders;
}

Expand All @@ -39,18 +40,23 @@ function generateAssetFileName(
): string {
const emittedName = name || 'asset';
return makeUnique(
renderNamePattern(output.assetFileNames, 'output.assetFileNames', {
hash() {
const hash = createHash();
hash.update(emittedName);
hash.update(':');
hash.update(source);
return hash.digest('hex').substr(0, 8);
renderNamePattern(
output.assetFileNames,
'output.assetFileNames',
{
hash() {
const hash = createHash();
hash.update(emittedName);
hash.update(':');
hash.update(source);
return hash.digest('hex').substr(0, 8);
},
ext: () => extname(emittedName).substr(1),
extname: () => extname(emittedName),
name: () => emittedName.substr(0, emittedName.length - extname(emittedName).length)
},
ext: () => extname(emittedName).substr(1),
extname: () => extname(emittedName),
name: () => emittedName.substr(0, emittedName.length - extname(emittedName).length)
}),
() => ({ name, source, type: 'asset' })
),
output.bundle
);
}
Expand Down Expand Up @@ -228,7 +234,7 @@ export class FileEmitter {

public setOutputBundle = (
outputBundle: OutputBundleWithPlaceholders,
assetFileNames: string,
assetFileNames: string | ((assetInfo: PreRenderedAsset) => string),
facadeChunkByModule: Map<Module, Chunk>
): void => {
this.output = {
Expand Down Expand Up @@ -334,6 +340,7 @@ export class FileEmitter {
const options = this.options;
output.bundle[fileName] = {
fileName,
name: consumedFile.name,
get isAsset(): true {
warnDeprecation(
'Accessing "isAsset" on files in the bundle is deprecated, please use "type === \'asset\'" instead',
Expand Down