Skip to content

Commit

Permalink
Add ability to use a function that returns a pattern string in all pl…
Browse files Browse the repository at this point in the history
…aces where you could use a pattern string before (#3658)

* # Das ist eine Kombination aus 4 Commits.
# Das ist die erste Commit-Beschreibung:

#2585

# Das ist Commit-Beschreibung #2:

Better Implementation now with a test

# Das ist Commit-Beschreibung #3:

Add: Tests

# Das ist Commit-Beschreibung #4:

Fix: License

* #2585

Better Implementation now with a test

Add: Tests

Fix: License

Now passing a object { replacements, meta() }

Add: docs and renamed property

* Corrected types

* add: test

* Upgraded test to include a chunk via dynamic import

* Fix: solo test

* Refine interfaces, types, docs and test

* Further refine docs

Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
  • Loading branch information
frank-dspeed and lukastaegert committed Jul 6, 2020
1 parent 8339c37 commit 19e50af
Show file tree
Hide file tree
Showing 23 changed files with 245 additions and 83 deletions.
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

0 comments on commit 19e50af

Please sign in to comment.