diff --git a/CHANGELOG.md b/CHANGELOG.md index 934a74fb0..e69b5765a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # rollup changelog +## 4.20.0 + +_2024-08-03_ + +### Features + +- Allow plugins to specify the original file name when emitting assets (#5596) + +### Pull Requests + +- [#5596](https://github.com/rollup/rollup/pull/5596): Add originalFIleName property to emitted assets (@lukastaegert) +- [#5599](https://github.com/rollup/rollup/pull/5599): chore(deps): update dependency eslint-plugin-unicorn to v55 (@renovate[bot], @lukastaegert) +- [#5600](https://github.com/rollup/rollup/pull/5600): chore(deps): lock file maintenance minor/patch updates (@renovate[bot], @lukastaegert) + ## 4.19.2 _2024-08-01_ diff --git a/browser/package.json b/browser/package.json index a10909c56..c3d45e850 100644 --- a/browser/package.json +++ b/browser/package.json @@ -1,6 +1,6 @@ { "name": "@rollup/browser", - "version": "4.19.2", + "version": "4.20.0", "description": "Next-generation ES module bundler browser build", "main": "dist/rollup.browser.js", "module": "dist/es/rollup.browser.js", diff --git a/docs/configuration-options/index.md b/docs/configuration-options/index.md index 2be0e907f..56d74c546 100755 --- a/docs/configuration-options/index.md +++ b/docs/configuration-options/index.md @@ -574,7 +574,8 @@ export default { ```typescript interface PreRenderedAsset { - name?: string; + name: string | undefined; + originalFileName: string | null; source: string | Uint8Array; type: 'asset'; } diff --git a/docs/plugin-development/index.md b/docs/plugin-development/index.md index 19c650296..c5e6b6086 100644 --- a/docs/plugin-development/index.md +++ b/docs/plugin-development/index.md @@ -670,7 +670,11 @@ interface SourceDescription { 可以被用来转换单个模块。为了防止额外的解析开销,例如,这个钩子已经使用 [`this.parse`](#this-parse) 生成了一个 AST,这个钩子可以选择返回一个 `{ code, ast, map }` 对象。`ast` 必须是一个标准的 ESTree AST,每个节点都有 `start` 和 `end` 属性。如果转换不移动代码,你可以通过将 `map` 设置为 `null` 来保留现有的 sourcemaps。否则,你可能需要生成源映射。请参阅 [源代码转换](#source-code-transformations) 部分。 +<<<<<<< HEAD 请注意,在观察模式下或明确使用缓存时,当重新构建时,此钩子的结果会被缓存,仅当模块的 `code` 发生更改或上次触发此钩子时添加了通过 `this.addWatchFile` 添加的文件时,才会再次触发该模块的钩子。 +======= +Note that in watch mode or when using the cache explicitly, the result of this hook is cached when rebuilding and the hook is only triggered again for a module `id` if either the `code` of the module has changed or a file has changed that was added via `this.addWatchFile` or `this.emitFile` the last time the hook was triggered for this module. +>>>>>>> df12edfea6e9c1a71bda1a01bed1ab787b7514d5 在所有其他情况下,将触发 [`shouldTransformCachedModule`](#shouldtransformcachedmodule) 钩子,该钩子可以访问缓存的模块。从 `shouldTransformCachedModule` 返回 `true` 将从缓存中删除该模块,并再次调用 `transform`。 @@ -908,8 +912,9 @@ function augmentWithDatePlugin() { ```typescript interface OutputAsset { fileName: string; - name?: string; + name: string | undefined; needsCodeReference: boolean; + originalFileName: string | null; source: string | Uint8Array; type: 'asset'; } @@ -1244,7 +1249,13 @@ function importMetaUrlCurrentModulePlugin() { 添加额外的文件以在监视模式下监视,以便更改这些文件将触发重建。`id` 可以是文件或目录的绝对路径,也可以是相对于当前工作目录的路径。此上下文函数可以在所有插件钩子中使用,除了 `closeBundle`。但是,如果 [`watch.skipWrite`](../configuration-options/index.md#watch-skipwrite) 设置为 `true`,则在 [输出生成钩子](#output-generation-hooks) 中使用它将不起作用。 +<<<<<<< HEAD **注意**:通常在监视模式下,为了提高重建速度,`transform` 钩子只会在给定模块的内容实际更改时触发。从 `transform` 钩子中使用 `this.addWatchFile` 将确保如果监视的文件更改,则也将重新评估此模块的 `transform` 钩子。 +======= +Note that when emitting assets that correspond to an existing file, it is recommended to set the `originalFileName` property in the [`this.emitFile`](#this-emitfile) call instead as that will not only watch the file but also make the connection transparent to other plugins. + +**Note:** Usually in watch mode to improve rebuild speed, the `transform` hook will only be triggered for a given module if its contents actually changed. Using `this.addWatchFile` from within the `transform` hook will make sure the `transform` hook is also reevaluated for this module if the watched file changes. +>>>>>>> df12edfea6e9c1a71bda1a01bed1ab787b7514d5 通常建议从依赖于监视文件的钩子中使用 `this.addWatchFile`。 @@ -1305,6 +1316,7 @@ interface EmittedAsset { type: 'asset'; name?: string; needsCodeReference?: boolean; + originalFileName?: string; fileName?: string; source?: string | Uint8Array; } @@ -1445,7 +1457,15 @@ import { foo } from './my-prebuilt-chunk.js'; 目前,产出预构建的块是一个基本功能。期待你的反馈。 +<<<<<<< HEAD 如果 `type` 是 _`asset`_,则它会产出一个具有给定 `source` 作为内容的任意新文件。可以通过 [`this.setAssetSource(referenceId, source)`](#this-setassetsource) 推迟设置 `source` 到稍后的时间,以便在构建阶段引用文件,同时在生成阶段为每个输出单独设置源。具有指定 `fileName` 的资产将始终生成单独的文件,而其他产出的资产可能会与现有资产进行去重,即使 `name` 不匹配。如果这样的资产没有被去重,则会使用 [`output.assetFileNames`](../configuration-options/index.md#output-assetfilenames) 名称模式。如果 `needsCodeReference` 设置为 `true`,并且此资产在输出中没有任何代码通过 `import.meta.ROLLUP_FILE_URL_referenceId` 引用,则 Rollup 将不会产出它。同时这也遵从通过除屑优化删除的引用,即如果相应的 `import.meta.ROLLUP_FILE_URL_referenceId` 是源代码的一部分,但实际上没有使用,引用被除屑优化给删除掉,也不会打包出相关的资源文件。 +======= +If the `type` is _`asset`_, then this emits an arbitrary new file with the given `source` as content. It is possible to defer setting the `source` via [`this.setAssetSource(referenceId, source)`](#this-setassetsource) to a later time to be able to reference a file during the build phase while setting the source separately for each output during the generate phase. Assets with a specified `fileName` will always generate separate files while other emitted assets may be deduplicated with existing assets if they have the same source even if the `name` does not match. If an asset without a `fileName` is not deduplicated, the [`output.assetFileNames`](../configuration-options/index.md#output-assetfilenames) name pattern will be used. + +If this asset corresponds to an actual file on disk, then `originalFileName` should be set to the absolute path of the file. In that case, this property will be passed on to subsequent plugin hooks that receive a `PreRenderedAsset` or an `OutputAsset` like [`generateBundle`](#generatebundle). In watch mode, Rollup will also automatically watch this file for changes and trigger a rebuild if it changes. Therefore, it is not necessary to call `this.addWatchFile` for this file. + +If `needsCodeReference` is set to `true` and this asset is not referenced by any code in the output via `import.meta.ROLLUP_FILE_URL_referenceId`, then Rollup will not emit it. This also respects references removed via tree-shaking, i.e. if the corresponding `import.meta.ROLLUP_FILE_URL_referenceId` is part of the source code but is not actually used and the reference is removed by tree-shaking, then the asset is not emitted. +>>>>>>> df12edfea6e9c1a71bda1a01bed1ab787b7514d5 ### this.error diff --git a/package-lock.json b/package-lock.json index 77bf41cd4..aebe0fe97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rollup", - "version": "4.19.2", + "version": "4.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rollup", - "version": "4.19.2", + "version": "4.20.0", "license": "MIT", "dependencies": { "@types/estree": "1.0.5" diff --git a/package.json b/package.json index b09b55e82..97bc79fdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "4.19.2", + "version": "4.20.0", "description": "Next-generation ES module bundler", "main": "dist/rollup.js", "module": "dist/es/rollup.js", diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 9eec93e68..bdcf86734 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -161,6 +161,7 @@ export interface EmittedAsset { fileName?: string; name?: string; needsCodeReference?: boolean; + originalFileName?: string | null; source?: string | Uint8Array; type: 'asset'; } @@ -821,6 +822,7 @@ export interface SerializedTimings { export interface PreRenderedAsset { name: string | undefined; + originalFileName: string | null; source: string | Uint8Array; type: 'asset'; } diff --git a/src/utils/FileEmitter.ts b/src/utils/FileEmitter.ts index caa249b54..8a9fee973 100644 --- a/src/utils/FileEmitter.ts +++ b/src/utils/FileEmitter.ts @@ -37,6 +37,7 @@ import { makeUnique, renderNamePattern } from './renderNamePattern'; function generateAssetFileName( name: string | undefined, source: string | Uint8Array, + originalFileName: string | null, sourceHash: string, outputOptions: NormalizedOutputOptions, bundle: OutputBundleWithPlaceholders @@ -45,7 +46,7 @@ function generateAssetFileName( return makeUnique( renderNamePattern( typeof outputOptions.assetFileNames === 'function' - ? outputOptions.assetFileNames({ name, source, type: 'asset' }) + ? outputOptions.assetFileNames({ name, originalFileName, source, type: 'asset' }) : outputOptions.assetFileNames, 'output.assetFileNames', { @@ -80,6 +81,7 @@ type ConsumedPrebuiltChunk = EmittedPrebuiltChunk & { type ConsumedAsset = EmittedAsset & { needsCodeReference: boolean; + originalFileName: string | null; referenceId: string; }; @@ -91,6 +93,7 @@ interface EmittedFile { [key: string]: unknown; fileName?: string; name?: string; + originalFileName?: string | null; type: EmittedFileType; } @@ -336,10 +339,15 @@ export class FileEmitter { emittedAsset.source === undefined ? undefined : getValidSource(emittedAsset.source, emittedAsset, null); + const originalFileName = emittedAsset.originalFileName || null; + if (typeof originalFileName === 'string') { + this.graph.watchFiles[originalFileName] = true; + } const consumedAsset: ConsumedAsset = { fileName: emittedAsset.fileName, name: emittedAsset.name, needsCodeReference: !!emittedAsset.needsCodeReference, + originalFileName, referenceId: '', source, type: 'asset' @@ -445,7 +453,7 @@ export class FileEmitter { source: string | Uint8Array, { bundle, fileNamesBySource, getHash, outputOptions }: FileEmitterOutput ): void { - let { fileName, needsCodeReference, referenceId } = consumedFile; + let { fileName, needsCodeReference, originalFileName, referenceId } = consumedFile; // Deduplicate assets if an explicit fileName is not provided if (!fileName) { @@ -455,6 +463,7 @@ export class FileEmitter { fileName = generateAssetFileName( consumedFile.name, source, + originalFileName, sourceHash, outputOptions, bundle @@ -475,6 +484,7 @@ export class FileEmitter { fileName, name: consumedFile.name, needsCodeReference, + originalFileName, source, type: 'asset' }; @@ -494,6 +504,7 @@ export class FileEmitter { const assetFileName = generateAssetFileName( consumedFile.name, consumedFile.source!, + consumedFile.originalFileName, sourceHash, outputOptions, bundle @@ -519,6 +530,7 @@ export class FileEmitter { fileName, name: usedConsumedFile!.name, needsCodeReference, + originalFileName: usedConsumedFile!.originalFileName, source: usedConsumedFile!.source!, type: 'asset' }; diff --git a/src/utils/renderChunks.ts b/src/utils/renderChunks.ts index 83f7b4098..7f33594df 100644 --- a/src/utils/renderChunks.ts +++ b/src/utils/renderChunks.ts @@ -388,7 +388,12 @@ function emitSourceMapAndGetComment( url = sourcemapBaseUrl ? new URL(sourcemapFileName, sourcemapBaseUrl).toString() : sourcemapFileName; - pluginDriver.emitFile({ fileName, source: map.toString(), type: 'asset' }); + pluginDriver.emitFile({ + fileName, + originalFileName: null, + source: map.toString(), + type: 'asset' + }); } return sourcemap === 'hidden' ? '' : `//# ${SOURCEMAPPING_URL}=${url}\n`; } diff --git a/test/chunking-form/samples/emit-file/filenames-function-patterns/_config.js b/test/chunking-form/samples/emit-file/filenames-function-patterns/_config.js index cacee4fa8..4d3fc03ac 100644 --- a/test/chunking-form/samples/emit-file/filenames-function-patterns/_config.js +++ b/test/chunking-form/samples/emit-file/filenames-function-patterns/_config.js @@ -38,6 +38,7 @@ module.exports = defineTest({ fileInfo, { name: 'test.txt', + originalFileName: null, source: 'hello world', type: 'asset' }, diff --git a/test/function/samples/emit-file/original-file-name/_config.js b/test/function/samples/emit-file/original-file-name/_config.js new file mode 100644 index 000000000..6ca9ba6db --- /dev/null +++ b/test/function/samples/emit-file/original-file-name/_config.js @@ -0,0 +1,49 @@ +const assert = require('node:assert'); +const path = require('node:path'); + +const ORIGINAL_FILE_NAME = path.join(__dirname, 'original.txt'); + +module.exports = defineTest({ + description: 'forwards the original file name to other hooks', + options: { + output: { + assetFileNames(info) { + if (info.name === 'with_original.txt') { + assert.strictEqual(info.originalFileName, ORIGINAL_FILE_NAME); + } else { + assert.strictEqual(info.originalFileName, null); + } + return info.name; + } + }, + plugins: [ + { + name: 'test', + buildStart() { + this.emitFile({ + type: 'asset', + name: 'with_original.txt', + originalFileName: ORIGINAL_FILE_NAME, + source: 'with original file name' + }); + this.emitFile({ + type: 'asset', + name: 'with_original_null.txt', + originalFileName: null, + source: 'with original file name null' + }); + this.emitFile({ + type: 'asset', + name: 'without_original.txt', + source: 'without original file name' + }); + }, + generateBundle(options, bundle) { + assert.strictEqual(bundle['with_original.txt'].originalFileName, ORIGINAL_FILE_NAME); + assert.strictEqual(bundle['with_original_null.txt'].originalFileName, null); + assert.strictEqual(bundle['without_original.txt'].originalFileName, null); + } + } + ] + } +}); diff --git a/test/function/samples/emit-file/original-file-name/main.js b/test/function/samples/emit-file/original-file-name/main.js new file mode 100644 index 000000000..69b8f8f79 --- /dev/null +++ b/test/function/samples/emit-file/original-file-name/main.js @@ -0,0 +1,13 @@ +let effect = false; + +var b = { + get a() { + effect = true; + } +}; + +function X() {} +X.prototype = b; +new X().a; + +assert.ok(effect); diff --git a/test/function/samples/sourcemap-true-generatebundle/_config.js b/test/function/samples/sourcemap-true-generatebundle/_config.js index f9abc1bfe..ee20d78a9 100644 --- a/test/function/samples/sourcemap-true-generatebundle/_config.js +++ b/test/function/samples/sourcemap-true-generatebundle/_config.js @@ -22,6 +22,7 @@ module.exports = main; fileName: 'main.js.map', name: undefined, needsCodeReference: false, + originalFileName: null, source: '{"version":3,"file":"main.js","sources":["main.js"],"sourcesContent":["export default 42;\\n"],"names":[],"mappings":";;AAAA,WAAe,EAAE;;;;"}', type: 'asset' diff --git a/test/watch/index.js b/test/watch/index.js index d725d2ae7..616583e50 100644 --- a/test/watch/index.js +++ b/test/watch/index.js @@ -1397,6 +1397,51 @@ describe('rollup.watch', function () { ]); }); + it('watches the original file name of emitted files', async () => { + await copy(path.join(SAMPLES_DIR, 'watch-files'), INPUT_DIR); + const WATCHED_ID = path.join(INPUT_DIR, 'watched'); + const OUTPUT_FILE_NAME = 'watched.txt'; + const OUTPUT_ID = path.join(OUTPUT_DIR, OUTPUT_FILE_NAME); + watcher = rollup.watch({ + input: ENTRY_FILE, + output: { + file: BUNDLE_FILE, + format: 'cjs', + exports: 'auto' + }, + plugins: [ + { + name: 'test', + buildStart() { + this.emitFile({ + type: 'asset', + fileName: OUTPUT_FILE_NAME, + originalFileName: WATCHED_ID, + source: readFileSync(WATCHED_ID).toString().trim() + }); + } + } + ] + }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(readFileSync(OUTPUT_ID).toString().trim(), 'initial'); + atomicWriteFileSync(WATCHED_ID, 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(readFileSync(OUTPUT_ID).toString().trim(), 'next'); + } + ]); + }); + describe('addWatchFile', () => { it('supports adding additional watch files in plugin hooks', async () => { const watchChangeIds = new Set();