From b7bb5e2c37d63dd53f739c2baa422a0075bcb16a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 29 Mar 2021 16:01:06 +0200 Subject: [PATCH] Improve absolute path handling (#4021) * Improve absolute path handling * Rename option to makeAbsoluteExternalsRelative and add documentation * Improve coverage and test names * Refine documentation text --- docs/05-plugin-development.md | 8 +- docs/999-big-list-of-options.md | 18 ++++- src/Chunk.ts | 6 +- src/ExternalModule.ts | 15 ++-- src/ModuleLoader.ts | 78 ++++++++++++++----- src/rollup/types.d.ts | 6 +- src/utils/options/mergeOptions.ts | 1 + src/utils/options/normalizeInputOptions.ts | 4 +- .../make-relative-false/_config.js | 45 +++++++++++ .../make-relative-false/_expected.js | 35 +++++++++ .../make-relative-false/main.js | 21 +++++ .../make-relative-false/nested/nested.js | 20 +++++ .../make-relative-relative/_config.js | 51 ++++++++++++ .../make-relative-relative/_expected.js | 35 +++++++++ .../make-relative-relative/main.js | 23 ++++++ .../make-relative-relative/nested/nested.js | 22 ++++++ .../relativeExisting.js | 0 .../make-relative-true/_config.js | 52 +++++++++++++ .../make-relative-true/_expected.js | 35 +++++++++ .../make-relative-true/main.js | 23 ++++++ .../make-relative-true/nested/nested.js | 22 ++++++ .../make-relative-true/relativeExisting.js | 0 test/form/samples/quote-id/_expected/amd.js | 4 +- test/form/samples/quote-id/_expected/cjs.js | 4 +- test/form/samples/quote-id/_expected/es.js | 4 +- .../form/samples/quote-id/_expected/system.js | 4 +- test/form/samples/quote-id/_expected/umd.js | 8 +- test/function/samples/options-hook/_config.js | 1 + test/misc/optionList.js | 4 +- 29 files changed, 499 insertions(+), 50 deletions(-) create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-false/_config.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-false/_expected.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-false/main.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-false/nested/nested.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-relative/_config.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-relative/_expected.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-relative/main.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-relative/nested/nested.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-relative/relativeExisting.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-true/_config.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-true/_expected.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-true/main.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-true/nested/nested.js create mode 100644 test/form/samples/make-absolute-externals-relative/make-relative-true/relativeExisting.js diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index 79a39e94951..eea6c2f5cc8 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -157,7 +157,7 @@ In case a dynamic import is not passed a string as argument, this hook gets acce Note that the return value of this hook will not be passed to `resolveId` afterwards; if you need access to the static resolution algorithm, you can use [`this.resolve(source, importer)`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean-custom-plugin-string-any--promiseid-string-external-boolean-modulesideeffects-boolean--no-treeshake-syntheticnamedexports-boolean--string-meta-plugin-string-any--null) on the plugin context. #### `resolveId` -Type: `(source: string, importer: string | undefined, options: {custom?: {[plugin: string]: any}) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}`
+Type: `(source: string, importer: string | undefined, options: {custom?: {[plugin: string]: any}) => string | false | null | {id: string, external?: boolean | "relative" | "absolute", moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}`
Kind: `async, first`
Previous Hook: [`buildStart`](guide/en/#buildstart) if we are resolving an entry point, [`moduleParsed`](guide/en/#moduleparsed) if we are resolving an import, or as fallback for [`resolveDynamicImport`](guide/en/#resolvedynamicimport). Additionally this hook can be triggered during the build phase from plugin hooks by calling [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string) to emit an entry point or at any time by calling [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean-custom-plugin-string-any--promiseid-string-external-boolean-modulesideeffects-boolean--no-treeshake-syntheticnamedexports-boolean--string-meta-plugin-string-any--null) to manually resolve an id.
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend). @@ -208,7 +208,7 @@ resolveId(source) { } ``` -Relative ids, i.e. starting with `./` or `../`, will **not** be renormalized when returning an object. If you want this behaviour, return an absolute file system location as `id` instead. +If `external` is `true`, then absolute ids will be converted to relative ids based on the user's choice for the [`makeAbsoluteExternalsRelative`](guide/en/#makeabsoluteexternalsrelative) option. This choice can be overridden by passing either `external: "relative"` to always convert an absolute id to a relative id or `external: "absolute"` to keep it as an absolute id. When returning an object, relative external ids, i.e. ids starting with `./` or `../`, will *not* be internally converted to an absolute id and converted back to a relative id in the output, but are instead included in the output unchanged. If you want relative ids to be renormalised and deduplicated instead, return an absolute file system location as `id` and choose `external: "relative"`. If `false` is returned for `moduleSideEffects` in the first hook that resolves a module id and no other module imports anything from this module, then this module will not be included even if the module would have side-effects. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `"no-treeshake"` is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the `treeshake.moduleSideEffects` option or default to `true`. The `load` and `transform` hooks can override this. @@ -695,8 +695,8 @@ An object containing potentially useful Rollup metadata: Use Rollup's internal acorn instance to parse code to an AST. -#### `this.resolve(source: string, importer?: string, options?: {skipSelf?: boolean, custom?: {[plugin: string]: any}}) => Promise<{id: string, external: boolean, moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: {[plugin: string]: any}} | null>` -Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If `null` is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user. +#### `this.resolve(source: string, importer?: string, options?: {skipSelf?: boolean, custom?: {[plugin: string]: any}}) => Promise<{id: string, external: boolean | "absolute", moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: {[plugin: string]: any}} | null>` +Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If `null` is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user. If an absolute external id is returned that should remain absolute in the output either via the [`makeAbsoluteExternalsRelative`](guide/en/#makeabsoluteexternalsrelative) option or by explicit plugin choice in the [`resolveId`](guide/en/#resolveid) hook, `external` will be `"absolute"` instead of `true`. If you pass `skipSelf: true`, then the `resolveId` hook of the plugin from which `this.resolve` is called will be skipped when resolving. When other plugins themselves also call `this.resolve` in their `resolveId` hooks with the *exact same `source` and `importer`* while handling the original `this.resolve` call, then the `resolveId` hook of the original plugin will be skipped for those calls as well. The rationale here is that the plugin already stated that it "does not know" how to resolve this particular combination of `source` and `importer` at this point in time. If you do not want this behaviour, do not use `skipSelf` but implement your own infinite loop prevention mechanism if necessary. diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index fc8785b7320..4e8c7d97432 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -319,6 +319,23 @@ buildWithCache() }) ``` +#### makeAbsoluteExternalsRelative +Type: `boolean | "ifRelativeSource"`
+CLI: `--makeAbsoluteExternalsRelative`/`--no-makeAbsoluteExternalsRelative`
+Default: `true` + +Determines if absolute external paths should be converted to relative paths in the output. This does not only apply to paths that are absolute in the source but also to paths that are resolved to an absolute path by either a plugin or Rollup core. + +For `true`, an external import like `import "/Users/Rollup/project/relative.js"` would be converted to a relative path. When converting an absolute path to a relative path, Rollup does *not* take the `file` or `dir` options into account, because those may not be present e.g. for builds using the JavaScript API. Instead, it assumes that the root of the generated bundle is located at the common shared parent directory of all modules that were included in the bundle. Assuming that the common parent directory of all modules is `"/Users/Rollup/project"`, the import from above would likely be converted to `import "./relative.js"` in the output. If the output chunk is itself nested in a sub-directory by choosing e.g. `chunkFileNames: "chunks/[name].js"`, the import would be `"../relative.js"`. + +As stated before, this would also apply to originally relative imports like `import "./relative.js"` that are resolved to an absolute path before they are marked as external by the [`external`](guide/en/#external) option. + +One common problem is that this mechanism will also apply to imports like `import "/absolute.js'"`, resulting in unexpected relative paths in the output. + +For this case, choosing `"ifRelativeSource"` will check if the original import was a relative import and only then convert it to a relative import in the output. Choosing `false` will keep all paths as absolute paths in the output. + +Note that when a relative path is directly marked as "external" using the [`external`](guide/en/#external) option, then it will be the same relative path in the output. When it is resolved first via a plugin or Rollup core and then marked as external, the above logic will apply. + #### onwarn Type: `(warning: RollupWarning, defaultHandler: (warning: string | RollupWarning) => void) => void;` @@ -360,7 +377,6 @@ export default { }; ``` - #### output.assetFileNames Type: `string | ((assetInfo: AssetInfo) => string)`
CLI: `--assetFileNames `
diff --git a/src/Chunk.ts b/src/Chunk.ts index 7fd282de596..17fd5b8623c 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -1250,7 +1250,11 @@ export default class Chunk { } } - private setIdentifierRenderResolutions({ format, interop, namespaceToStringTag }: NormalizedOutputOptions) { + private setIdentifierRenderResolutions({ + format, + interop, + namespaceToStringTag + }: NormalizedOutputOptions) { const syntheticExports = new Set(); for (const exportName of this.getExportNames()) { const exportVariable = this.exportsByName[exportName]; diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index f5ffba4bbb2..0b81685625a 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -7,7 +7,7 @@ import { } from './rollup/types'; import { EMPTY_ARRAY } from './utils/blank'; import { makeLegal } from './utils/identifierHelpers'; -import { isAbsolute, normalize, relative } from './utils/path'; +import { normalize, relative } from './utils/path'; export default class ExternalModule { chunk: void; @@ -23,7 +23,6 @@ export default class ExternalModule { nameSuggestions: { [name: string]: number }; reexported = false; renderPath: string = undefined as any; - renormalizeRenderPath = false; suggestedVariableName: string; used = false; variableName = ''; @@ -32,7 +31,8 @@ export default class ExternalModule { private readonly options: NormalizedInputOptions, public readonly id: string, hasModuleSideEffects: boolean | 'no-treeshake', - meta: CustomPluginOptions + meta: CustomPluginOptions, + public renormalizeRenderPath: boolean ) { this.execIndex = Infinity; this.suggestedVariableName = makeLegal(id.split(/[\\/]/).pop()!); @@ -76,12 +76,9 @@ export default class ExternalModule { this.renderPath = typeof options.paths === 'function' ? options.paths(this.id) : options.paths[this.id]; if (!this.renderPath) { - if (!isAbsolute(this.id)) { - this.renderPath = this.id; - } else { - this.renderPath = normalize(relative(inputBase, this.id)); - this.renormalizeRenderPath = true; - } + this.renderPath = this.renormalizeRenderPath + ? normalize(relative(inputBase, this.id)) + : this.id; } return this.renderPath; } diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index b8be285588d..cad73ea8473 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -6,8 +6,9 @@ import { CustomPluginOptions, EmittedChunk, HasModuleSideEffects, + ModuleOptions, NormalizedInputOptions, - PartialResolvedId, + PartialNull, Plugin, ResolvedId, ResolveIdResult, @@ -27,7 +28,7 @@ import { errUnresolvedImportTreatedAsExternal } from './utils/error'; import { readFile } from './utils/fs'; -import { isRelative, resolve } from './utils/path'; +import { isAbsolute, isRelative, resolve } from './utils/path'; import { PluginDriver } from './utils/PluginDriver'; import relativeId from './utils/relativeId'; import { resolveId } from './utils/resolveId'; @@ -41,6 +42,11 @@ export interface UnresolvedModule { name: string | null; } +type NormalizedResolveIdWithoutDefaults = Partial> & { + external?: boolean | 'absolute'; + id: string; +}; + export class ModuleLoader { private readonly hasModuleSideEffects: HasModuleSideEffects; private readonly implicitEntryModules = new Set(); @@ -160,9 +166,11 @@ export class ModuleLoader { source ) ); - } + }; - private addDefaultsToResolvedId(resolvedId: PartialResolvedId | null): ResolvedId | null { + private addDefaultsToResolvedId( + resolvedId: NormalizedResolveIdWithoutDefaults | null + ): ResolvedId | null { if (!resolvedId) { return null; } @@ -172,7 +180,7 @@ export class ModuleLoader { id: resolvedId.id, meta: resolvedId.meta || EMPTY_OBJECT, moduleSideEffects: - resolvedId.moduleSideEffects ?? this.hasModuleSideEffects(resolvedId.id, external), + resolvedId.moduleSideEffects ?? this.hasModuleSideEffects(resolvedId.id, !!external), syntheticNamedExports: resolvedId.syntheticNamedExports ?? false }; } @@ -338,19 +346,21 @@ export class ModuleLoader { resolvedId: ResolvedId ): Promise { if (resolvedId.external) { - if (!this.modulesById.has(resolvedId.id)) { + const { external, id, moduleSideEffects, meta } = resolvedId; + if (!this.modulesById.has(id)) { this.modulesById.set( - resolvedId.id, + id, new ExternalModule( this.options, - resolvedId.id, - resolvedId.moduleSideEffects, - resolvedId.meta + id, + moduleSideEffects, + meta, + external !== 'absolute' && isAbsolute(id) ) ); } - const externalModule = this.modulesById.get(resolvedId.id); + const externalModule = this.modulesById.get(id); if (!(externalModule instanceof ExternalModule)) { return error(errInternalIdCannotBeExternal(source, importer)); } @@ -385,27 +395,45 @@ export class ModuleLoader { resolveIdResult: ResolveIdResult, importer: string | undefined, source: string - ): (PartialResolvedId & { external: boolean }) | null { + ): NormalizedResolveIdWithoutDefaults | null { + const { makeAbsoluteExternalsRelative } = this.options; if (resolveIdResult) { if (typeof resolveIdResult === 'object') { + const external = + resolveIdResult.external || this.options.external(resolveIdResult.id, importer, true); return { ...resolveIdResult, external: - resolveIdResult.external || this.options.external(resolveIdResult.id, importer, true) + external && + (external === 'relative' || + !isAbsolute(resolveIdResult.id) || + (external === true && + isNotAbsoluteExternal(resolveIdResult.id, source, makeAbsoluteExternalsRelative)) || + 'absolute') }; } + const external = this.options.external(resolveIdResult, importer, true); return { - external, - id: external ? normalizeRelativeExternalId(resolveIdResult, importer) : resolveIdResult + external: + external && + (isNotAbsoluteExternal(resolveIdResult, source, makeAbsoluteExternalsRelative) || + 'absolute'), + id: + external && makeAbsoluteExternalsRelative + ? normalizeRelativeExternalId(resolveIdResult, importer) + : resolveIdResult }; } - const id = normalizeRelativeExternalId(source, importer); + + const id = makeAbsoluteExternalsRelative + ? normalizeRelativeExternalId(source, importer) + : source; if (resolveIdResult !== false && !this.options.external(id, importer, true)) { return null; } return { - external: true, + external: isNotAbsoluteExternal(id, source, makeAbsoluteExternalsRelative) || 'absolute', id }; } @@ -469,7 +497,9 @@ export class ModuleLoader { } return this.fetchModule( this.addDefaultsToResolvedId( - typeof resolveIdResult === 'object' ? resolveIdResult : { id: resolveIdResult } + typeof resolveIdResult === 'object' + ? (resolveIdResult as NormalizedResolveIdWithoutDefaults) + : { id: resolveIdResult } )!, undefined, isEntry @@ -541,3 +571,15 @@ function addChunkNamesToModule( } } } + +function isNotAbsoluteExternal( + id: string, + source: string, + makeAbsoluteExternalsRelative: boolean | 'ifRelativeSource' +) { + return ( + makeAbsoluteExternalsRelative === true || + (makeAbsoluteExternalsRelative === 'ifRelativeSource' && isRelative(source)) || + !isAbsolute(id) + ); +} diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index bd448dce28b..b9dda715aa8 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -219,7 +219,7 @@ export interface PluginContextMeta { } export interface ResolvedId extends ModuleOptions { - external: boolean; + external: boolean | 'absolute'; id: string; } @@ -228,7 +228,7 @@ export interface ResolvedIdMap { } interface PartialResolvedId extends Partial> { - external?: boolean; + external?: boolean | 'absolute' | 'relative'; id: string; } @@ -528,6 +528,7 @@ export interface InputOptions { /** @deprecated Use the "inlineDynamicImports" output option instead. */ inlineDynamicImports?: boolean; input?: InputOption; + makeAbsoluteExternalsRelative?: boolean | 'ifRelativeSource'; /** @deprecated Use the "manualChunks" output option instead. */ manualChunks?: ManualChunksOption; moduleContext?: ((id: string) => string | null | undefined) | { [id: string]: string }; @@ -554,6 +555,7 @@ export interface NormalizedInputOptions { /** @deprecated Use the "inlineDynamicImports" output option instead. */ inlineDynamicImports: boolean | undefined; input: string[] | { [entryAlias: string]: string }; + makeAbsoluteExternalsRelative: boolean | 'ifRelativeSource'; /** @deprecated Use the "manualChunks" output option instead. */ manualChunks: ManualChunksOption | undefined; moduleContext: (id: string) => string; diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index 843109df900..822e26c0f8f 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -106,6 +106,7 @@ function mergeInputOptions( external: getExternal(config, overrides), inlineDynamicImports: getOption('inlineDynamicImports'), input: getOption('input') || [], + makeAbsoluteExternalsRelative: getOption('makeAbsoluteExternalsRelative'), manualChunks: getOption('manualChunks'), moduleContext: getOption('moduleContext'), onwarn: getOnWarn(config, defaultOnWarnHandler), diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index 595575b9ca6..b85e848cc74 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -49,6 +49,8 @@ export function normalizeInputOptions( external: getIdMatcher(config.external as ExternalOption), inlineDynamicImports: getInlineDynamicImports(config, onwarn, strictDeprecations), input: getInput(config), + makeAbsoluteExternalsRelative: + (config.makeAbsoluteExternalsRelative as boolean | 'ifRelativeSource' | undefined) ?? true, manualChunks: getManualChunks(config, onwarn, strictDeprecations), moduleContext: getModuleContext(config, context), onwarn, @@ -262,7 +264,7 @@ const getTreeshake = ( warn ), propertyReadSideEffects: - configTreeshake.propertyReadSideEffects === 'always' && 'always' || + (configTreeshake.propertyReadSideEffects === 'always' && 'always') || configTreeshake.propertyReadSideEffects !== false, tryCatchDeoptimization: configTreeshake.tryCatchDeoptimization !== false, unknownGlobalSideEffects: configTreeshake.unknownGlobalSideEffects !== false diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-false/_config.js b/test/form/samples/make-absolute-externals-relative/make-relative-false/_config.js new file mode 100644 index 00000000000..b364476bf6c --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-false/_config.js @@ -0,0 +1,45 @@ +const path = require('path'); +const assert = require('assert'); + +const ID_MAIN = path.join(__dirname, 'main.js'); + +module.exports = { + description: 'does not normalize external paths when set to false', + options: { + makeAbsoluteExternalsRelative: false, + external(id) { + if ( + [ + './relativeUnresolved.js', + '../relativeUnresolved.js', + '/absolute.js', + '/pluginAbsolute.js' + ].includes(id) + ) + return true; + }, + plugins: { + async buildStart() { + const testExternal = async (source, expected) => + assert.deepStrictEqual((await this.resolve(source, ID_MAIN)).external, expected, source); + + await testExternal('./relativeUnresolved.js', true); + await testExternal('/absolute.js', 'absolute'); + await testExternal('./pluginDirect.js', true); + await testExternal('/pluginDifferentAbsolute.js', 'absolute'); + await testExternal('./pluginTrue.js', 'absolute'); + await testExternal('./pluginForceAbsolute.js', 'absolute'); + await testExternal('./pluginForceRelative.js', true); + }, + resolveId(source) { + if (source.endsWith('/pluginDirect.js')) return false; + if (source.endsWith('/pluginDifferentAbsolute.js')) return '/pluginAbsolute.js'; + if (source.endsWith('/pluginTrue.js')) return { id: '/pluginTrue.js', external: true }; + if (source.endsWith('/pluginForceAbsolute.js')) + return { id: '/pluginForceAbsolute.js', external: 'absolute' }; + if (source.endsWith('/pluginForceRelative.js')) + return { id: path.join(__dirname, 'pluginForceRelative.js'), external: 'relative' }; + } + } + } +}; diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-false/_expected.js b/test/form/samples/make-absolute-externals-relative/make-relative-false/_expected.js new file mode 100644 index 00000000000..563d9d96c7c --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-false/_expected.js @@ -0,0 +1,35 @@ +import { relativeUnresolved as relativeUnresolved$1 } from './relativeUnresolved.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect as pluginDirect$1 } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginAbsolute.js'; +import { pluginTrue } from '/pluginTrue.js'; +import { pluginForceAbsolute } from '/pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; +import { relativeUnresolved } from '../relativeUnresolved.js'; +import { pluginDirect } from '../pluginDirect.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); + +console.log( + 'main', + relativeUnresolved$1, + relativeMissing, + relativeExisting, + absolute, + pluginDirect$1, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-false/main.js b/test/form/samples/make-absolute-externals-relative/make-relative-false/main.js new file mode 100644 index 00000000000..ba2f8c7ce1b --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-false/main.js @@ -0,0 +1,21 @@ +import { relativeUnresolved } from './relativeUnresolved.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from './pluginTrue.js'; +import { pluginForceAbsolute } from './pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; +import './nested/nested.js'; + +console.log( + 'main', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-false/nested/nested.js b/test/form/samples/make-absolute-externals-relative/make-relative-false/nested/nested.js new file mode 100644 index 00000000000..bef0c20d7ae --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-false/nested/nested.js @@ -0,0 +1,20 @@ +import { relativeUnresolved } from '../relativeUnresolved.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from '../pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from '../pluginTrue.js'; +import { pluginForceAbsolute } from '../pluginForceAbsolute.js'; +import { pluginForceRelative } from '../pluginForceRelative.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-relative/_config.js b/test/form/samples/make-absolute-externals-relative/make-relative-relative/_config.js new file mode 100644 index 00000000000..2c5cfd74053 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-relative/_config.js @@ -0,0 +1,51 @@ +const path = require('path'); +const assert = require('assert'); + +const ID_MAIN = path.join(__dirname, 'main.js'); + +module.exports = { + description: + 'only normalizes external paths that were originally relative when set to "ifRelativeSource"', + options: { + makeAbsoluteExternalsRelative: 'ifRelativeSource', + external(id) { + if ( + [ + './relativeUnresolved.js', + '../relativeUnresolved.js', + path.join(__dirname, 'relativeMissing.js'), + path.join(__dirname, 'relativeExisting.js'), + '/absolute.js', + '/pluginAbsolute.js' + ].includes(id) + ) + return true; + }, + plugins: { + async buildStart() { + const testExternal = async (source, expected) => + assert.deepStrictEqual((await this.resolve(source, ID_MAIN)).external, expected, source); + + await testExternal('./relativeUnresolved.js', true); + await testExternal('./relativeMissing.js', true); + await testExternal('./relativeExisting.js', true); + await testExternal('/absolute.js', 'absolute'); + await testExternal('./pluginDirect.js', true); + await testExternal('/pluginDifferentAbsolute.js', 'absolute'); + await testExternal('./pluginTrue.js', true); + await testExternal('./pluginForceAbsolute.js', 'absolute'); + await testExternal('./pluginForceRelative.js', true); + }, + resolveId(source) { + if (source.endsWith('/pluginDirect.js')) return false; + if (source.endsWith('/pluginDifferentAbsolute.js')) return '/pluginAbsolute.js'; + if (source.endsWith('/pluginTrue.js')) + return { id: path.join(__dirname, 'pluginTrue.js'), external: true }; + if (source.endsWith('/pluginForceAbsolute.js')) + return { id: '/pluginForceAbsolute.js', external: 'absolute' }; + if (source.endsWith('/pluginForceRelative.js')) + return { id: path.join(__dirname, 'pluginForceRelative.js'), external: 'relative' }; + } + } + } +}; diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-relative/_expected.js b/test/form/samples/make-absolute-externals-relative/make-relative-relative/_expected.js new file mode 100644 index 00000000000..193ec36b78b --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-relative/_expected.js @@ -0,0 +1,35 @@ +import { relativeUnresolved } from './relativeUnresolved.js'; +import { relativeMissing } from './relativeMissing.js'; +import { relativeExisting } from './relativeExisting.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginAbsolute.js'; +import { pluginTrue } from './pluginTrue.js'; +import { pluginForceAbsolute } from '/pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); + +console.log( + 'main', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-relative/main.js b/test/form/samples/make-absolute-externals-relative/make-relative-relative/main.js new file mode 100644 index 00000000000..3b1936d48c0 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-relative/main.js @@ -0,0 +1,23 @@ +import { relativeUnresolved } from './relativeUnresolved.js'; +import { relativeMissing } from './relativeMissing.js'; +import { relativeExisting } from './relativeExisting.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from './pluginTrue.js'; +import { pluginForceAbsolute } from './pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; +import './nested/nested.js'; + +console.log( + 'main', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-relative/nested/nested.js b/test/form/samples/make-absolute-externals-relative/make-relative-relative/nested/nested.js new file mode 100644 index 00000000000..76ab993b451 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-relative/nested/nested.js @@ -0,0 +1,22 @@ +import { relativeUnresolved } from '../relativeUnresolved.js'; +import { relativeMissing } from '../relativeMissing.js'; +import { relativeExisting } from '../relativeExisting.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from '../pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from '../pluginTrue.js'; +import { pluginForceAbsolute } from '../pluginForceAbsolute.js'; +import { pluginForceRelative } from '../pluginForceRelative.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-relative/relativeExisting.js b/test/form/samples/make-absolute-externals-relative/make-relative-relative/relativeExisting.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-true/_config.js b/test/form/samples/make-absolute-externals-relative/make-relative-true/_config.js new file mode 100644 index 00000000000..100574d7e54 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-true/_config.js @@ -0,0 +1,52 @@ +const path = require('path'); +const assert = require('assert'); + +const ID_MAIN = path.join(__dirname, 'main.js'); + +module.exports = { + description: 'normalizes both relative and absolute external paths when set to true', + options: { + makeAbsoluteExternalsRelative: true, + external(id) { + if ( + [ + './relativeUnresolved.js', + '../relativeUnresolved.js', + path.join(__dirname, 'relativeMissing.js'), + path.join(__dirname, 'relativeExisting.js'), + path.join(__dirname, 'absolute.js'), + path.join(__dirname, 'pluginAbsolute.js') + ].includes(id) + ) + return true; + }, + plugins: { + async buildStart() { + const testExternal = async (source, expected) => + assert.deepStrictEqual((await this.resolve(source, ID_MAIN)).external, expected, source); + + await testExternal('./relativeUnresolved.js', true); + await testExternal('./relativeMissing.js', true); + await testExternal('./relativeExisting.js', true); + await testExternal('/absolute.js', true); + await testExternal('./pluginDirect.js', true); + await testExternal('/pluginDifferentAbsolute.js', true); + await testExternal('./pluginTrue.js', true); + await testExternal('./pluginForceAbsolute.js', 'absolute'); + await testExternal('./pluginForceRelative.js', true); + }, + resolveId(source) { + if (source.endsWith('/pluginDirect.js')) return false; + if (source.endsWith('/pluginDifferentAbsolute.js')) + return path.join(__dirname, 'pluginAbsolute.js'); + if (source.endsWith('/pluginTrue.js')) + return { id: path.join(__dirname, 'pluginTrue.js'), external: true }; + if (source.endsWith('/pluginForceAbsolute.js')) + return { id: '/pluginForceAbsolute.js', external: 'absolute' }; + if (source.endsWith('/pluginForceRelative.js')) + return { id: path.join(__dirname, 'pluginForceRelative.js'), external: 'relative' }; + if (source === '/absolute.js') return path.join(__dirname, 'absolute.js'); + } + } + } +}; diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-true/_expected.js b/test/form/samples/make-absolute-externals-relative/make-relative-true/_expected.js new file mode 100644 index 00000000000..2bbfcb451a0 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-true/_expected.js @@ -0,0 +1,35 @@ +import { relativeUnresolved } from './relativeUnresolved.js'; +import { relativeMissing } from './relativeMissing.js'; +import { relativeExisting } from './relativeExisting.js'; +import { absolute } from './absolute.js'; +import { pluginDirect } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from './pluginAbsolute.js'; +import { pluginTrue } from './pluginTrue.js'; +import { pluginForceAbsolute } from '/pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); + +console.log( + 'main', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-true/main.js b/test/form/samples/make-absolute-externals-relative/make-relative-true/main.js new file mode 100644 index 00000000000..3b1936d48c0 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-true/main.js @@ -0,0 +1,23 @@ +import { relativeUnresolved } from './relativeUnresolved.js'; +import { relativeMissing } from './relativeMissing.js'; +import { relativeExisting } from './relativeExisting.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from './pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from './pluginTrue.js'; +import { pluginForceAbsolute } from './pluginForceAbsolute.js'; +import { pluginForceRelative } from './pluginForceRelative.js'; +import './nested/nested.js'; + +console.log( + 'main', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-true/nested/nested.js b/test/form/samples/make-absolute-externals-relative/make-relative-true/nested/nested.js new file mode 100644 index 00000000000..76ab993b451 --- /dev/null +++ b/test/form/samples/make-absolute-externals-relative/make-relative-true/nested/nested.js @@ -0,0 +1,22 @@ +import { relativeUnresolved } from '../relativeUnresolved.js'; +import { relativeMissing } from '../relativeMissing.js'; +import { relativeExisting } from '../relativeExisting.js'; +import { absolute } from '/absolute.js'; +import { pluginDirect } from '../pluginDirect.js'; +import { pluginDifferentAbsolute } from '/pluginDifferentAbsolute.js'; +import { pluginTrue } from '../pluginTrue.js'; +import { pluginForceAbsolute } from '../pluginForceAbsolute.js'; +import { pluginForceRelative } from '../pluginForceRelative.js'; + +console.log( + 'nested', + relativeUnresolved, + relativeMissing, + relativeExisting, + absolute, + pluginDirect, + pluginDifferentAbsolute, + pluginTrue, + pluginForceAbsolute, + pluginForceRelative +); diff --git a/test/form/samples/make-absolute-externals-relative/make-relative-true/relativeExisting.js b/test/form/samples/make-absolute-externals-relative/make-relative-true/relativeExisting.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/form/samples/quote-id/_expected/amd.js b/test/form/samples/quote-id/_expected/amd.js index 2e1243729a0..aba63d8d282 100644 --- a/test/form/samples/quote-id/_expected/amd.js +++ b/test/form/samples/quote-id/_expected/amd.js @@ -1,6 +1,6 @@ define(['quoted\'\ \ -\
\
external1', 'quoted\'\ \ -\
\
external2', 'C:\\File\\Path.js'], function (quoted_____external1, quoted_____external2, Path_js) { 'use strict'; +\
\
external1', './quoted\'\ \ +\
\
external2', './C:/File/Path'], function (quoted_____external1, quoted_____external2, Path_js) { 'use strict'; console.log(quoted_____external1.foo, quoted_____external2.bar, Path_js.baz); diff --git a/test/form/samples/quote-id/_expected/cjs.js b/test/form/samples/quote-id/_expected/cjs.js index be514723275..9468de1d0d0 100644 --- a/test/form/samples/quote-id/_expected/cjs.js +++ b/test/form/samples/quote-id/_expected/cjs.js @@ -2,8 +2,8 @@ var quoted_____external1 = require('quoted\'\ \ \
\
external1'); -var quoted_____external2 = require('quoted\'\ \ +var quoted_____external2 = require('./quoted\'\ \ \
\
external2'); -var Path_js = require('C:\\File\\Path.js'); +var Path_js = require('./C:/File/Path.js'); console.log(quoted_____external1.foo, quoted_____external2.bar, Path_js.baz); diff --git a/test/form/samples/quote-id/_expected/es.js b/test/form/samples/quote-id/_expected/es.js index cb0fe35580d..182f5ee556a 100644 --- a/test/form/samples/quote-id/_expected/es.js +++ b/test/form/samples/quote-id/_expected/es.js @@ -1,7 +1,7 @@ import { foo } from 'quoted\'\ \ \
\
external1'; -import { bar } from 'quoted\'\ \ +import { bar } from './quoted\'\ \ \
\
external2'; -import { baz } from 'C:\\File\\Path.js'; +import { baz } from './C:/File/Path.js'; console.log(foo, bar, baz); diff --git a/test/form/samples/quote-id/_expected/system.js b/test/form/samples/quote-id/_expected/system.js index 7572577c40d..2043821f04c 100644 --- a/test/form/samples/quote-id/_expected/system.js +++ b/test/form/samples/quote-id/_expected/system.js @@ -1,6 +1,6 @@ System.register('Q', ['quoted\'\ \ -\
\
external1', 'quoted\'\ \ -\
\
external2', 'C:\\File\\Path.js'], function () { +\
\
external1', './quoted\'\ \ +\
\
external2', './C:/File/Path.js'], function () { 'use strict'; var foo, bar, baz; return { diff --git a/test/form/samples/quote-id/_expected/umd.js b/test/form/samples/quote-id/_expected/umd.js index 92870cbe872..f59f3c56cdd 100644 --- a/test/form/samples/quote-id/_expected/umd.js +++ b/test/form/samples/quote-id/_expected/umd.js @@ -1,10 +1,10 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('quoted\'\ \ -\
\
external1'), require('quoted\'\ \ -\
\
external2'), require('C:\\File\\Path.js')) : +\
\
external1'), require('./quoted\'\ \ +\
\
external2'), require('./C:/File/Path.js')) : typeof define === 'function' && define.amd ? define(['quoted\'\ \ -\
\
external1', 'quoted\'\ \ -\
\
external2', 'C:\\File\\Path.js'], factory) : +\
\
external1', './quoted\'\ \ +\
\
external2', './C:/File/Path'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.quotedExternal1, global.quotedExternal2, global.quotedExternal3)); }(this, (function (quoted_____external1, quoted_____external2, Path_js) { 'use strict'; diff --git a/test/function/samples/options-hook/_config.js b/test/function/samples/options-hook/_config.js index b217bd91263..8c2289935d3 100644 --- a/test/function/samples/options-hook/_config.js +++ b/test/function/samples/options-hook/_config.js @@ -19,6 +19,7 @@ module.exports = { context: 'undefined', experimentalCacheExpiry: 10, input: ['used'], + makeAbsoluteExternalsRelative: true, perf: false, plugins: [ { diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 05392922320..72c376d9724 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,6 +1,6 @@ exports.input = - 'acorn, acornInjectPlugins, cache, context, experimentalCacheExpiry, external, inlineDynamicImports, input, manualChunks, moduleContext, onwarn, perf, plugins, preserveEntrySignatures, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; + 'acorn, acornInjectPlugins, cache, context, experimentalCacheExpiry, external, inlineDynamicImports, input, makeAbsoluteExternalsRelative, manualChunks, moduleContext, onwarn, perf, plugins, preserveEntrySignatures, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; exports.flags = - 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; + 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, makeAbsoluteExternalsRelative, manualChunks, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, hoistTransitiveImports, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, preserveModules, preserveModulesRoot, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict, systemNullSetters, validate';