diff --git a/.github/workflows/build-and-tests.yml b/.github/workflows/build-and-tests.yml index 40bcb1a9a..fb4ac0d67 100644 --- a/.github/workflows/build-and-tests.yml +++ b/.github/workflows/build-and-tests.yml @@ -120,10 +120,14 @@ jobs: cross: zig - host: ubuntu-latest target: armv7-linux-androideabi - cross: zig + cross: napi + # There are compile issues when using the cache with Android + cache-cargo: false - host: ubuntu-latest target: aarch64-linux-android - cross: zig + cross: napi + # There are compile issues when using the cache with Android + cache-cargo: false - host: ubuntu-latest target: riscv64gc-unknown-linux-gnu setup: | @@ -210,6 +214,7 @@ jobs: uses: openharmony-rs/setup-ohos-sdk@52d50de65363f895558a43de0dceb1f8e3679b1c # v0.2.3 - name: Restore Cargo cache uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + if: matrix.settings.cache-cargo != false with: path: | ~/.cargo/registry/index/ @@ -258,7 +263,7 @@ jobs: if: ${{ !matrix.settings.docker && !matrix.settings.build }} shell: bash - name: Save Cargo cache - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' && matrix.settings.cache-cargo != false uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: | diff --git a/docs/plugin-development/index.md b/docs/plugin-development/index.md index 05ac021b4..cc6b5ff31 100644 --- a/docs/plugin-development/index.md +++ b/docs/plugin-development/index.md @@ -1427,7 +1427,7 @@ interface EmittedAsset { 当生成代码块或者资源文件时,可以提供 `name` 或者 `fileName`。如果提供了 `fileName`,它将不会被修改,而是直接作为生成文件的名称,如果这样会导致冲突,则会抛出错误。否则,如果提供了 `name`,它将被用作对应的 [`output.chunkFileNames`](../configuration-options/index.md#output-chunkfilenames) 或者 [`output.assetFileNames`](../configuration-options/index.md#output-assetfilenames) 模式中的 `[name]` 的替换,可能会在文件名的末尾添加一个唯一的数字,以避免冲突。如果既没有提供 `name` 也没有提供 `fileName`,则会使用默认名称。预构建的代码块必须始终有一个 `fileName`。 -你可以通过 `import.meta.ROLLUP_FILE_URL_referenceId` 在任何由 [`load`](#load) 或 [`transform`](#transform) 插件钩子返回的代码中引用生成的文件的 URL。有关更多详细信息和示例,请参见 [File URL](#file-urls)。 +你可以通过 `import.meta.ROLLUP_FILE_URL_referenceId`(返回一个字符串)或 `import.meta.ROLLUP_FILE_URL_OBJ_referenceId`(返回一个 URL 对象),在任何由 [`load`](#load) 或 [`transform`](#transform) 插件钩子返回的代码中引用生成的文件的 URL。有关更多详细信息和示例,请参见 [File URL](#file-urls)。 替换 `import.meta.ROLLUP_FILE_URL_referenceId` 的生成代码可以通过 [`resolveFileUrl`](#resolvefileurl) 插件钩子进行自定义。你也可以使用 [`this.getFileName(referenceId)`](#this-getfilename) 在文件名可用时确定文件名。如果没有显式设置文件名,则 @@ -2123,6 +2123,17 @@ export const size = 6; 如果你构建上诉代码,则主块和工作块都将通过共享块从 `config.js` 共享代码。这使我们能够利用浏览器缓存来减少传输数据并加快加载工作块的速度。 +你也可以使用 `import.meta.ROLLUP_FILE_URL_OBJ_referenceId` 直接获取一个 URL 对象,而不是一个字符串。当你需要 URL 对象本身时,这会更高效,因为它避免了重复创建对象: + +```js +// 使用 ROLLUP_FILE_URL(返回字符串,需要通过 new URL() 包装使用) +const urlString = import.meta.ROLLUP_FILE_URL_referenceId; +const urlObject = new URL(urlString); + +// 使用 ROLLUP_FILE_URL_OBJ(直接返回 URL 对象) +const urlObject = import.meta.ROLLUP_FILE_URL_OBJ_referenceId; +``` + ## 转换器 {#Transformers} 转换器插件(即返回 `transform` 函数以进行例如转换非 JS 文件的转换器)应支持 `options.include` 和 `options.exclude`,两者都可以是 minimatch 模式或 minimatch 模式数组。如果省略 `options.include` 或其长度为零,则默认情况下应包括文件;否则,只有 ID 与其中一个模式匹配时才应包括它们。 diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index 5993f031f..5a4672e83 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -16,6 +16,7 @@ import type * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; const FILE_PREFIX = 'ROLLUP_FILE_URL_'; +const FILE_OBJ_PREFIX = 'ROLLUP_FILE_URL_OBJ_'; const IMPORT = 'import'; export default class MetaProperty extends NodeBase { @@ -32,8 +33,12 @@ export default class MetaProperty extends NodeBase { meta: { name }, metaProperty } = this; - if (name === IMPORT && metaProperty?.startsWith(FILE_PREFIX)) { - return outputPluginDriver.getFileName(metaProperty.slice(FILE_PREFIX.length)); + if (name === IMPORT) { + if (metaProperty?.startsWith(FILE_OBJ_PREFIX)) { + return outputPluginDriver.getFileName(metaProperty.slice(FILE_OBJ_PREFIX.length)); + } else if (metaProperty?.startsWith(FILE_PREFIX)) { + return outputPluginDriver.getFileName(metaProperty.slice(FILE_PREFIX.length)); + } } return null; } @@ -59,7 +64,9 @@ export default class MetaProperty extends NodeBase { parent instanceof MemberExpression && typeof parent.propertyKey === 'string' ? parent.propertyKey : null); - if (metaProperty?.startsWith(FILE_PREFIX)) { + if (metaProperty?.startsWith(FILE_OBJ_PREFIX)) { + this.referenceId = metaProperty.slice(FILE_OBJ_PREFIX.length); + } else if (metaProperty?.startsWith(FILE_PREFIX)) { this.referenceId = metaProperty.slice(FILE_PREFIX.length); } } @@ -87,10 +94,11 @@ export default class MetaProperty extends NodeBase { if (referenceId) { const fileName = pluginDriver.getFileName(referenceId); const relativePath = normalize(relative(dirname(chunkId), fileName)); + const isUrlObject = !!metaProperty?.startsWith(FILE_OBJ_PREFIX); const replacement = pluginDriver.hookFirstSync('resolveFileUrl', [ { chunkId, fileName, format, moduleId, referenceId, relativePath } - ]) || relativeUrlMechanisms[format](relativePath); + ]) || relativeUrlMechanisms[format](relativePath, isUrlObject); code.overwrite( (parent as MemberExpression).start, @@ -126,7 +134,9 @@ export default class MetaProperty extends NodeBase { ): void { this.preliminaryChunkId = preliminaryChunkId; const accessedGlobals = ( - this.metaProperty?.startsWith(FILE_PREFIX) ? accessedFileUrlGlobals : accessedMetaUrlGlobals + this.metaProperty?.startsWith(FILE_PREFIX) || this.metaProperty?.startsWith(FILE_OBJ_PREFIX) + ? accessedFileUrlGlobals + : accessedMetaUrlGlobals )[format]; if (accessedGlobals.length > 0) { this.scope.addAccessedGlobals(accessedGlobals, accessedGlobalsByScope); @@ -154,13 +164,15 @@ const accessedFileUrlGlobals = { umd: ['document', 'require', 'URL'] }; -const getResolveUrl = (path: string, URL = 'URL') => `new ${URL}(${path}).href`; +const getResolveUrl = (path: string, asObject: boolean, URL = 'URL') => + `new ${URL}(${path})${asObject ? '' : '.href'}`; -const getRelativeUrlFromDocument = (relativePath: string, umd = false) => +const getRelativeUrlFromDocument = (relativePath: string, asObject: boolean, umd = false) => getResolveUrl( `'${escapeId(relativePath)}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' - }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI` + }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, + asObject ); const getGenericImportMetaMechanism = @@ -174,10 +186,11 @@ const getGenericImportMetaMechanism = : 'undefined'; }; -const getFileUrlFromFullPath = (path: string) => `require('u' + 'rl').pathToFileURL(${path}).href`; +const getFileUrlFromFullPath = (path: string, asObject: boolean) => + `require('u' + 'rl').pathToFileURL(${path})${asObject ? '' : '.href'}`; -const getFileUrlFromRelativePath = (path: string) => - getFileUrlFromFullPath(`__dirname + '/${escapeId(path)}'`); +const getFileUrlFromRelativePath = (path: string, asObject: boolean) => + getFileUrlFromFullPath(`__dirname + '/${escapeId(path)}'`, asObject); const getUrlFromDocument = (chunkId: string, umd = false) => `${ @@ -186,42 +199,39 @@ const getUrlFromDocument = (chunkId: string, umd = false) => chunkId )}', document.baseURI).href)`; -const relativeUrlMechanisms: Record string> = { - amd: relativePath => { +const relativeUrlMechanisms: Record< + InternalModuleFormat, + (relativePath: string, asObject: boolean) => string +> = { + amd: (relativePath, asObject: boolean) => { if (relativePath[0] !== '.') relativePath = './' + relativePath; - return getResolveUrl(`require.toUrl('${escapeId(relativePath)}'), document.baseURI`); + return getResolveUrl(`require.toUrl('${escapeId(relativePath)}'), document.baseURI`, asObject); }, - cjs: relativePath => - `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( - relativePath - )} : ${getRelativeUrlFromDocument(relativePath)})`, - es: relativePath => getResolveUrl(`'${escapeId(relativePath)}', import.meta.url`), - iife: relativePath => getRelativeUrlFromDocument(relativePath), - system: relativePath => getResolveUrl(`'${escapeId(relativePath)}', module.meta.url`), - umd: relativePath => - `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( - relativePath - )} : ${getRelativeUrlFromDocument(relativePath, true)})` + cjs: (relativePath, asObject: boolean) => + `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath(relativePath, asObject)} : ${getRelativeUrlFromDocument(relativePath, asObject)})`, + es: (relativePath, asObject: boolean) => + getResolveUrl(`'${escapeId(relativePath)}', import.meta.url`, asObject), + iife: (relativePath, asObject: boolean) => getRelativeUrlFromDocument(relativePath, asObject), + system: (relativePath, asObject: boolean) => + getResolveUrl(`'${escapeId(relativePath)}', module.meta.url`, asObject), + umd: (relativePath, asObject: boolean) => + `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath(relativePath, asObject)} : ${getRelativeUrlFromDocument(relativePath, asObject, true)})` }; const importMetaMechanisms: Record< string, (property: string | null, options: { chunkId: string; snippets: GenerateCodeSnippets }) => string > = { - amd: getGenericImportMetaMechanism(() => getResolveUrl(`module.uri, document.baseURI`)), + amd: getGenericImportMetaMechanism(() => getResolveUrl(`module.uri, document.baseURI`, false)), cjs: getGenericImportMetaMechanism( chunkId => - `(typeof document === 'undefined' ? ${getFileUrlFromFullPath( - '__filename' - )} : ${getUrlFromDocument(chunkId)})` + `(typeof document === 'undefined' ? ${getFileUrlFromFullPath('__filename', false)} : ${getUrlFromDocument(chunkId)})` ), iife: getGenericImportMetaMechanism(chunkId => getUrlFromDocument(chunkId)), system: (property, { snippets: { getPropertyAccess } }) => property === null ? `module.meta` : `module.meta${getPropertyAccess(property)}`, umd: getGenericImportMetaMechanism( chunkId => - `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromFullPath( - '__filename' - )} : ${getUrlFromDocument(chunkId, true)})` + `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromFullPath('__filename', false)} : ${getUrlFromDocument(chunkId, true)})` ) }; diff --git a/test/chunking-form/samples/resolve-file-url-obj/_config.js b/test/chunking-form/samples/resolve-file-url-obj/_config.js new file mode 100644 index 000000000..db08e25af --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_config.js @@ -0,0 +1,29 @@ +module.exports = defineTest({ + description: 'allows to use ROLLUP_FILE_URL_OBJ to get URL objects directly', + options: { + plugins: [ + { + resolveId(id) { + if (id === 'url-test') { + return id; + } + }, + load(id) { + if (id === 'url-test') { + const assetId = this.emitFile({ + type: 'asset', + name: 'test.txt', + source: 'test content' + }); + return ` + // Test string URL replacement + export const assetString = import.meta.ROLLUP_FILE_URL_${assetId}; + // Test URL object replacement + export const assetObject = import.meta.ROLLUP_FILE_URL_OBJ_${assetId}; + `; + } + } + } + ] + } +}); diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/assets/test-r6af3lUh.txt b/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/assets/test-r6af3lUh.txt new file mode 100644 index 000000000..08cf61014 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/assets/test-r6af3lUh.txt @@ -0,0 +1 @@ +test content \ No newline at end of file diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/main.js b/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/main.js new file mode 100644 index 000000000..63b443d58 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/amd/main.js @@ -0,0 +1,11 @@ +define(['require'], (function (require) { 'use strict'; + + // Test string URL replacement + const assetString = new URL(require.toUrl('./assets/test-r6af3lUh.txt'), document.baseURI).href; + // Test URL object replacement + const assetObject = new URL(require.toUrl('./assets/test-r6af3lUh.txt'), document.baseURI); + + console.log('String URL:', assetString); + console.log('URL Object:', assetObject); + +})); diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/assets/test-r6af3lUh.txt b/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/assets/test-r6af3lUh.txt new file mode 100644 index 000000000..08cf61014 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/assets/test-r6af3lUh.txt @@ -0,0 +1 @@ +test content \ No newline at end of file diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/main.js b/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/main.js new file mode 100644 index 000000000..3a34d3a4d --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/cjs/main.js @@ -0,0 +1,9 @@ +'use strict'; + +// Test string URL replacement + const assetString = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__dirname + '/assets/test-r6af3lUh.txt').href : new URL('assets/test-r6af3lUh.txt', document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI).href); + // Test URL object replacement + const assetObject = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__dirname + '/assets/test-r6af3lUh.txt') : new URL('assets/test-r6af3lUh.txt', document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI)); + +console.log('String URL:', assetString); +console.log('URL Object:', assetObject); diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/es/assets/test-r6af3lUh.txt b/test/chunking-form/samples/resolve-file-url-obj/_expected/es/assets/test-r6af3lUh.txt new file mode 100644 index 000000000..08cf61014 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/es/assets/test-r6af3lUh.txt @@ -0,0 +1 @@ +test content \ No newline at end of file diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/es/main.js b/test/chunking-form/samples/resolve-file-url-obj/_expected/es/main.js new file mode 100644 index 000000000..6e88b3e35 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/es/main.js @@ -0,0 +1,7 @@ +// Test string URL replacement + const assetString = new URL('assets/test-r6af3lUh.txt', import.meta.url).href; + // Test URL object replacement + const assetObject = new URL('assets/test-r6af3lUh.txt', import.meta.url); + +console.log('String URL:', assetString); +console.log('URL Object:', assetObject); diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/system/assets/test-r6af3lUh.txt b/test/chunking-form/samples/resolve-file-url-obj/_expected/system/assets/test-r6af3lUh.txt new file mode 100644 index 000000000..08cf61014 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/system/assets/test-r6af3lUh.txt @@ -0,0 +1 @@ +test content \ No newline at end of file diff --git a/test/chunking-form/samples/resolve-file-url-obj/_expected/system/main.js b/test/chunking-form/samples/resolve-file-url-obj/_expected/system/main.js new file mode 100644 index 000000000..44063d5c0 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/_expected/system/main.js @@ -0,0 +1,16 @@ +System.register([], (function (exports, module) { + 'use strict'; + return { + execute: (function () { + + // Test string URL replacement + const assetString = new URL('assets/test-r6af3lUh.txt', module.meta.url).href; + // Test URL object replacement + const assetObject = new URL('assets/test-r6af3lUh.txt', module.meta.url); + + console.log('String URL:', assetString); + console.log('URL Object:', assetObject); + + }) + }; +})); diff --git a/test/chunking-form/samples/resolve-file-url-obj/main.js b/test/chunking-form/samples/resolve-file-url-obj/main.js new file mode 100644 index 000000000..e5beaebf8 --- /dev/null +++ b/test/chunking-form/samples/resolve-file-url-obj/main.js @@ -0,0 +1,4 @@ +import { assetString, assetObject } from 'url-test'; + +console.log('String URL:', assetString); +console.log('URL Object:', assetObject); \ No newline at end of file