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
augmentChunkHash plugin hook #2921
Changes from 9 commits
5561b69
55bbad4
3550bde
1657402
49a1670
52ae7ff
cb4475d
35053bd
237254c
b9ad5d1
e2aac9a
d4dcc58
5800c03
373f6e9
57ccc8c
74f8305
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,6 +187,23 @@ Kind: `async, parallel` | |
|
||
Called initially each time `bundle.generate()` or `bundle.write()` is called. To get notified when generation has completed, use the `generateBundle` and `renderError` hooks. | ||
|
||
#### `augmentChunkHash` | ||
Type: `(preRenderedChunk: PreRenderedChunk) => any`<br> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
Kind: `sync, sequential` | ||
|
||
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. | ||
|
||
The following plugin will invalidate the hash of chunk `foo` with the timestamp of the last build: | ||
|
||
```javascript | ||
// rollup.config.js | ||
augmentChunkHash(chunkInfo) { | ||
if(chunkInfo.name === 'foo') { | ||
return Date.now(); | ||
} | ||
} | ||
``` | ||
|
||
#### `resolveDynamicImport` | ||
Type: `(specifier: string | ESTree.Node, importer: string) => string | false | null | {id: string, external?: boolean}`<br> | ||
Kind: `async, first` | ||
|
@@ -257,7 +274,7 @@ Kind: `sync, first` | |
Allows to customize how Rollup handles `import.meta` and `import.meta.someProperty`, in particular `import.meta.url`. In ES modules, `import.meta` is an object and `import.meta.url` contains the URL of the current module, e.g. `http://server.net/bundle.js` for browsers or `file:///path/to/bundle.js` in Node. | ||
|
||
By default for formats other than ES modules, Rollup replaces `import.meta.url` with code that attempts to match this behaviour by returning the dynamic URL of the current chunk. Note that all formats except CommonJS and UMD assume that they run in a browser environment where `URL` and `document` are available. For other properties, `import.meta.someProperty` is replaced with `undefined` while `import.meta` is replaced with an object containing a `url` property. | ||
|
||
This behaviour can be changed—also for ES modules—via this hook. For each occurrence of `import.meta<.someProperty>`, this hook is called with the name of the property or `null` if `import.meta` is accessed directly. For example, the following code will resolve `import.meta.url` using the relative path of the original module to the current working directory and again resolve this path against the base URL of the current document at runtime: | ||
|
||
```javascript | ||
|
@@ -352,9 +369,9 @@ Emits a new file that is included in the build output and returns a `referenceId | |
``` | ||
|
||
In both cases, either a `name` or a `fileName` can be supplied. If a `fileName` is provided, it will be used unmodified as the name of the generated file, throwing an error if this causes a conflict. Otherwise if a `name` is supplied, this will be used as substitution for `[name]` in the corresponding [`output.chunkFileNames`](guide/en/#outputchunkfilenames) or [`output.assetFileNames`](guide/en/#outputassetfilenames) pattern, possibly adding a unique number to the end of the file name to avoid conflicts. If neither a `name` nor `fileName` is supplied, a default name will be used. | ||
|
||
You can reference the URL of an emitted file in any code returned by a [`load`](guide/en/#load) or [`transform`](guide/en/#transform) plugin hook via `import.meta.ROLLUP_FILE_URL_referenceId`. See [File URLs](guide/en/#file-urls) for more details and an example. | ||
|
||
The generated code that replaces `import.meta.ROLLUP_FILE_URL_referenceId` can be customized via the [`resolveFileUrl`](guide/en/#resolvefileurl) plugin hook. You can also use [`this.getFileName(referenceId)`](guide/en/#thisgetfilenamereferenceid-string--string) to determine the file name as soon as it is available | ||
|
||
If the `type` is *`chunk`*, then this emits a new chunk with the given module id as entry point. This will not result in duplicate modules in the graph, instead if necessary, existing chunks will be split or a facade chunk with reexports will be created. Chunks with a specified `fileName` will always generate separate chunks while other emitted chunks may be deduplicated with existing chunks even if the `name` does not match. If such a chunk is not deduplicated, the [`output.chunkFileNames`](guide/en/#outputchunkfilenames) name pattern will be used. | ||
|
@@ -437,15 +454,15 @@ The `position` argument is a character index where the warning was raised. If pr | |
☢️ These context utility functions have been deprecated and may be removed in a future Rollup version. | ||
|
||
- `this.emitAsset(assetName: string, source: string) => string` - _**Use [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string)**_ - Emits a custom file that is included in the build output, returning an `assetReferenceId` that can be used to reference the emitted file. You can defer setting the source if you provide it later via [`this.setAssetSource(assetReferenceId, source)`](guide/en/#thissetassetsourceassetreferenceid-string-source-string--buffer--void). A string or Buffer source must be set for each asset through either method or an error will be thrown on generate completion. | ||
|
||
Emitted assets will follow the [`output.assetFileNames`](guide/en/#outputassetfilenames) naming scheme. You can reference the URL of the file in any code returned by a [`load`](guide/en/#load) or [`transform`](guide/en/#transform) plugin hook via `import.meta.ROLLUP_ASSET_URL_assetReferenceId`. | ||
|
||
The generated code that replaces `import.meta.ROLLUP_ASSET_URL_assetReferenceId` can be customized via the [`resolveFileUrl`](guide/en/#resolvefileurl) plugin hook. Once the asset has been finalized during `generate`, you can also use [`this.getFileName(assetReferenceId)`](guide/en/#thisgetfilenamereferenceid-string--string) to determine the file name. | ||
|
||
- `this.emitChunk(moduleId: string, options?: {name?: string}) => string` - _**Use [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string)**_ - Emits a new chunk with the given module as entry point. This will not result in duplicate modules in the graph, instead if necessary, existing chunks will be split. It returns a `chunkReferenceId` that can be used to later access the generated file name of the chunk. | ||
|
||
Emitted chunks will follow the [`output.chunkFileNames`](guide/en/#outputchunkfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames) naming scheme. If a `name` is provided, this will be used for the `[name]` file name placeholder, otherwise the name will be derived from the file name. If a `name` is provided, this name must not conflict with any other entry point names unless the entry points reference the same entry module. You can reference the URL of the emitted chunk in any code returned by a [`load`](guide/en/#load) or [`transform`](guide/en/#transform) plugin hook via `import.meta.ROLLUP_CHUNK_URL_chunkReferenceId`. | ||
|
||
The generated code that replaces `import.meta.ROLLUP_CHUNK_URL_chunkReferenceId` can be customized via the [`resolveFileUrl`](guide/en/#resolvefileurl) plugin hook. Once the chunk has been rendered during `generate`, you can also use [`this.getFileName(chunkReferenceId)`](guide/en/#thisgetfilenamereferenceid-string--string) to determine the file name. | ||
|
||
- `this.getAssetFileName(assetReferenceId: string) => string` - _**Use [`this.getFileName`](guide/en/#thisgetfilenamereferenceid-string--string)**_ - Get the file name of an asset, according to the `assetFileNames` output option pattern. The file name will be relative to `outputOptions.dir`. | ||
|
@@ -494,7 +511,7 @@ image.src = logo; | |
document.body.appendChild(image); | ||
``` | ||
|
||
Similar to assets, emitted chunks can be referenced from within JS code via `import.meta.ROLLUP_FILE_URL_referenceId` as well. | ||
Similar to assets, emitted chunks can be referenced from within JS code via `import.meta.ROLLUP_FILE_URL_referenceId` as well. | ||
|
||
The following example will detect imports prefixed with `register-paint-worklet:` and generate the necessary code and separate chunk to generate a CSS paint worklet. Note that this will only work in modern browsers and will only work if the output format is set to `esm`. | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,6 +136,7 @@ export default class Chunk { | |
return chunk; | ||
} | ||
|
||
augmentedHash?: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From how this is generated, I would say this is more like a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense and will actually accomplish the lazy behaviour |
||
entryModules: Module[] = []; | ||
execIndex: number; | ||
exportMode: 'none' | 'named' | 'default' = 'named'; | ||
|
@@ -341,6 +342,9 @@ export default class Chunk { | |
if (this.renderedHash) return this.renderedHash; | ||
if (!this.renderedSource) return ''; | ||
const hash = sha256(); | ||
if (this.augmentedHash) { | ||
hash.update(this.augmentedHash); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the |
||
} | ||
hash.update(this.renderedSource.toString()); | ||
hash.update( | ||
this.getExportNames() | ||
|
@@ -801,7 +805,6 @@ export default class Chunk { | |
|
||
private computeContentHashWithDependencies(addons: Addons, options: OutputOptions): string { | ||
const hash = sha256(); | ||
|
||
hash.update( | ||
[addons.intro, addons.outro, addons.banner, addons.footer].map(addon => addon || '').join(':') | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import { | |
OutputChunk, | ||
OutputOptions, | ||
Plugin, | ||
PreRenderedChunk, | ||
RollupBuild, | ||
RollupCache, | ||
RollupOutput, | ||
|
@@ -147,14 +148,44 @@ export function setWatcher(watcher: RollupWatcher) { | |
curWatcher = watcher; | ||
} | ||
|
||
function augmentChunkHashes(graph: Graph, chunks: Chunk[]): void { | ||
for (let i = 0; i < chunks.length; i++) { | ||
const chunk = chunks[i]; | ||
const facadeModule = chunk.facadeModule; | ||
const preRenderedChunk = { | ||
dynamicImports: chunk.getDynamicImportIds(), | ||
exports: chunk.getExportNames(), | ||
facadeModuleId: facadeModule && facadeModule.id, | ||
imports: chunk.getImportIds(), | ||
isDynamicEntry: facadeModule !== null && facadeModule.dynamicallyImportedBy.length > 0, | ||
isEntry: facadeModule !== null && facadeModule.isEntryPoint, | ||
modules: chunk.renderedModules, | ||
get name() { | ||
return chunk.getChunkName(); | ||
} | ||
} as PreRenderedChunk; | ||
const augmentedHash = graph.pluginDriver.hookReduceValueSync( | ||
'augmentChunkHash', | ||
'', | ||
[preRenderedChunk], | ||
(hashForChunk, pluginHash) => { | ||
if (pluginHash) { | ||
hashForChunk += pluginHash; | ||
} | ||
return hashForChunk; | ||
} | ||
); | ||
chunk.augmentedHash = augmentedHash; | ||
} | ||
} | ||
|
||
function assignChunksToBundle( | ||
chunks: Chunk[], | ||
outputBundle: OutputBundleWithPlaceholders | ||
): OutputBundle { | ||
for (let i = 0; i < chunks.length; i++) { | ||
const chunk = chunks[i]; | ||
const facadeModule = chunk.facadeModule; | ||
|
||
outputBundle[chunk.id as string] = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the end I ended up duplicating this info since a great part of it must be recalculated since internal Ids change from |
||
code: undefined as any, | ||
dynamicImports: chunk.getDynamicImportIds(), | ||
|
@@ -252,6 +283,9 @@ export default function rollup(rawInputOptions: GenericConfigObject): Promise<Ro | |
); | ||
optimized = true; | ||
} | ||
|
||
augmentChunkHashes(graph, chunks); | ||
|
||
assignChunkIds( | ||
chunks, | ||
inputOptions, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -327,6 +327,7 @@ interface OnWriteOptions extends OutputOptions { | |
} | ||
|
||
export interface PluginHooks { | ||
augmentChunkHash: (this: PluginContext, chunk: PreRenderedChunk) => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the correct type would be something like |
||
buildEnd: (this: PluginContext, err?: Error) => Promise<void> | void; | ||
buildStart: (this: PluginContext, options: InputOptions) => Promise<void> | void; | ||
generateBundle: ( | ||
|
@@ -499,11 +500,10 @@ export interface RenderedModule { | |
renderedLength: number; | ||
} | ||
|
||
export interface RenderedChunk { | ||
export interface PreRenderedChunk { | ||
isidrok marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dynamicImports: string[]; | ||
exports: string[]; | ||
facadeModuleId: string | null; | ||
fileName: string; | ||
imports: string[]; | ||
isDynamicEntry: boolean; | ||
isEntry: boolean; | ||
|
@@ -513,6 +513,10 @@ export interface RenderedChunk { | |
name: string; | ||
} | ||
|
||
export interface RenderedChunk extends PreRenderedChunk { | ||
fileName: string; | ||
} | ||
|
||
export interface OutputChunk extends RenderedChunk { | ||
code: string; | ||
map?: SourceMap; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module.exports = { | ||
description: 'augmentChunkHash updates hashes across all modules when returning something', | ||
options1: { | ||
input: 'main', | ||
output: { | ||
format: 'esm', | ||
entryFileNames: '[name]-[hash].js', | ||
chunkFileNames: '[name]-[hash].js' | ||
}, | ||
plugins: [ | ||
{ | ||
augmentChunkHash() { | ||
return 'bar'; | ||
} | ||
} | ||
] | ||
}, | ||
options2: { | ||
input: 'main', | ||
output: { | ||
format: 'esm', | ||
entryFileNames: '[name]-[hash].js', | ||
chunkFileNames: '[name]-[hash].js' | ||
}, | ||
plugins: [ | ||
{ | ||
augmentChunkHash() {} | ||
} | ||
] | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const foo = 'foo'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import {foo} from './dep'; | ||
console.log(foo); | ||
console.log('main'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1014,6 +1014,36 @@ describe('hooks', () => { | |
}); | ||
}); | ||
|
||
it('supports augmentChunkHash hook', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be interesting to convert some of those tests to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
let augmentChunkHashCalls = 0; | ||
return rollup | ||
.rollup({ | ||
input: 'input', | ||
plugins: [ | ||
loader({ | ||
input: `alert('hello')` | ||
}), | ||
{ | ||
augmentChunkHash(update) { | ||
augmentChunkHashCalls++; | ||
assert(this.meta); | ||
assert(this.meta.rollupVersion); | ||
} | ||
} | ||
] | ||
}) | ||
.then(bundle => | ||
bundle.generate({ | ||
format: 'esm', | ||
dir: 'dist', | ||
entryFileNames: '[name]-[hash].js' | ||
}) | ||
) | ||
.then(output => { | ||
assert.equal(augmentChunkHashCalls, 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think this is too important to test as calling is implicitly tested with any test that also tests that the hash is changed, which is what is actually important to us. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the end I left this one inside hook tests and created a file-hashes one with tests the rest of them |
||
}); | ||
}); | ||
|
||
describe('deprecated', () => { | ||
it('passes bundle & output object to ongenerate & onwrite hooks, with deprecation warnings', () => { | ||
let deprecationCnt = 0; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding documentation! It would be nice to stick with the alphabetical ordering of the hooks and move this up.