Skip to content

Commit

Permalink
Rename option to makeAbsoluteExternalsRelative and add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Mar 29, 2021
1 parent 486a13f commit 72ff786
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 43 deletions.
8 changes: 4 additions & 4 deletions docs/05-plugin-development.md
Expand Up @@ -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}`<br>
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}`<br>
Kind: `async, first`<br>
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.<br>
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend).
Expand Down Expand Up @@ -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 do not want this behaviour, return an absolute file system location as `id` instead 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.

Expand Down Expand Up @@ -694,8 +694,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.
Expand Down
18 changes: 17 additions & 1 deletion docs/999-big-list-of-options.md
Expand Up @@ -319,6 +319,23 @@ buildWithCache()
})
```

#### makeAbsoluteExternalsRelative
Type: `boolean | "ifRelativeSource"`<br>
CLI: `--makeAbsoluteExternalsRelative`/`--no-makeAbsoluteExternalsRelative`<br>
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;`

Expand Down Expand Up @@ -360,7 +377,6 @@ export default {
};
```


#### output.assetFileNames
Type: `string | ((assetInfo: AssetInfo) => string)`<br>
CLI: `--assetFileNames <pattern>`<br>
Expand Down
35 changes: 13 additions & 22 deletions src/ModuleLoader.ts
Expand Up @@ -391,24 +391,12 @@ export class ModuleLoader {
}
}

/* For plugins when resolveIdResult.id is absolute (otherwise external is true)
external | normalizeExternalPaths | result
true | true | true
true | 'relative' | 'absolute'
true | false | 'absolute'
'normalize' | true | true
'normalize' | 'relative' | true
'normalize' | false | true
'absolute' | true | 'absolute'
'absolute' | 'relative' | 'absolute'
'absolute' | false | 'absolute'
*/
private getNormalizedResolvedIdWithoutDefaults(
resolveIdResult: ResolveIdResult,
importer: string | undefined,
source: string
): NormalizedResolveIdWithoutDefaults | null {
const { normalizeExternalPaths } = this.options;
const { makeAbsoluteExternalsRelative } = this.options;
if (resolveIdResult) {
if (typeof resolveIdResult === 'object') {
const external =
Expand All @@ -417,10 +405,10 @@ export class ModuleLoader {
...resolveIdResult,
external:
external &&
(external === 'normalize' ||
(external === 'relative' ||
!isAbsolute(resolveIdResult.id) ||
(external === true &&
isNotAbsoluteExternal(resolveIdResult.id, source, normalizeExternalPaths)) ||
isNotAbsoluteExternal(resolveIdResult.id, source, makeAbsoluteExternalsRelative)) ||
'absolute')
};
}
Expand All @@ -429,20 +417,23 @@ export class ModuleLoader {
return {
external:
external &&
(isNotAbsoluteExternal(resolveIdResult, source, normalizeExternalPaths) || 'absolute'),
(isNotAbsoluteExternal(resolveIdResult, source, makeAbsoluteExternalsRelative) ||
'absolute'),
id:
external && normalizeExternalPaths
external && makeAbsoluteExternalsRelative
? normalizeRelativeExternalId(resolveIdResult, importer)
: resolveIdResult
};
}

const id = normalizeExternalPaths ? normalizeRelativeExternalId(source, importer) : source;
const id = makeAbsoluteExternalsRelative
? normalizeRelativeExternalId(source, importer)
: source;
if (resolveIdResult !== false && !this.options.external(id, importer, true)) {
return null;
}
return {
external: isNotAbsoluteExternal(id, source, normalizeExternalPaths) || 'absolute',
external: isNotAbsoluteExternal(id, source, makeAbsoluteExternalsRelative) || 'absolute',
id
};
}
Expand Down Expand Up @@ -584,11 +575,11 @@ function addChunkNamesToModule(
function isNotAbsoluteExternal(
id: string,
source: string,
normalizeExternalPaths: boolean | 'relative'
makeAbsoluteExternalsRelative: boolean | 'ifRelativeSource'
) {
return (
normalizeExternalPaths === true ||
(normalizeExternalPaths === 'relative' && isRelative(source)) ||
makeAbsoluteExternalsRelative === true ||
(makeAbsoluteExternalsRelative === 'ifRelativeSource' && isRelative(source)) ||
!isAbsolute(id)
);
}
6 changes: 3 additions & 3 deletions src/rollup/types.d.ts
Expand Up @@ -228,7 +228,7 @@ export interface ResolvedIdMap {
}

interface PartialResolvedId extends Partial<PartialNull<ModuleOptions>> {
external?: boolean | 'absolute' | 'normalize';
external?: boolean | 'absolute' | 'relative';
id: string;
}

Expand Down Expand Up @@ -528,10 +528,10 @@ 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 };
normalizeExternalPaths?: boolean | 'relative';
onwarn?: WarningHandlerWithDefault;
perf?: boolean;
plugins?: Plugin[];
Expand All @@ -555,10 +555,10 @@ 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;
normalizeExternalPaths: boolean | 'relative';
onwarn: WarningHandler;
perf: boolean;
plugins: Plugin[];
Expand Down
2 changes: 1 addition & 1 deletion src/utils/options/mergeOptions.ts
Expand Up @@ -106,9 +106,9 @@ function mergeInputOptions(
external: getExternal(config, overrides),
inlineDynamicImports: getOption('inlineDynamicImports'),
input: getOption('input') || [],
makeAbsoluteExternalsRelative: getOption('makeAbsoluteExternalsRelative'),
manualChunks: getOption('manualChunks'),
moduleContext: getOption('moduleContext'),
normalizeExternalPaths: getOption('normalizeExternalPaths'),
onwarn: getOnWarn(config, defaultOnWarnHandler),
perf: getOption('perf'),
plugins: ensureArray(config.plugins) as Plugin[],
Expand Down
4 changes: 2 additions & 2 deletions src/utils/options/normalizeInputOptions.ts
Expand Up @@ -49,10 +49,10 @@ 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),
normalizeExternalPaths:
(config.normalizeExternalPaths as boolean | 'relative' | undefined) ?? true,
onwarn,
perf: (config.perf as boolean | undefined) || false,
plugins: ensureArray(config.plugins) as Plugin[],
Expand Down
@@ -1,21 +1,35 @@
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: {
normalizeExternalPaths: false,
makeAbsoluteExternalsRelative: false,
external(id) {
if (['./relativeUnresolved.js', '../relativeUnresolved.js', '/absolute.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('./pluginTrue.js', 'absolute');
await testExternal('./pluginAbsolute.js', 'absolute');
await testExternal('./pluginNormalize.js', true);
},
resolveId(source) {
if (source.endsWith('/pluginDirect.js')) return false;
if (source.endsWith('/pluginTrue.js')) return { id: '/pluginTrue.js', external: true };
if (source.endsWith('/pluginAbsolute.js'))
return { id: '/pluginAbsolute.js', external: 'absolute' };
if (source.endsWith('/pluginNormalize.js'))
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'normalize' };
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'relative' };
}
}
}
Expand Down
@@ -1,10 +1,13 @@
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 "relative"',
'only normalizes external paths that were originally relative when set to "ifRelativeSource"',
options: {
normalizeExternalPaths: 'relative',
makeAbsoluteExternalsRelative: 'ifRelativeSource',
external(id) {
if (
[
Expand All @@ -18,14 +21,27 @@ module.exports = {
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('./pluginTrue.js', true);
await testExternal('./pluginAbsolute.js', 'absolute');
await testExternal('./pluginNormalize.js', true);
},
resolveId(source) {
if (source.endsWith('/pluginDirect.js')) return false;
if (source.endsWith('/pluginTrue.js'))
return { id: path.join(__dirname, 'pluginTrue.js'), external: true };
if (source.endsWith('/pluginAbsolute.js'))
return { id: '/pluginAbsolute.js', external: 'absolute' };
if (source.endsWith('/pluginNormalize.js'))
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'normalize' };
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'relative' };
}
}
}
Expand Down
@@ -1,9 +1,12 @@
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: {
normalizeExternalPaths: true,
makeAbsoluteExternalsRelative: true,
external(id) {
if (
[
Expand All @@ -17,14 +20,27 @@ module.exports = {
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('./pluginTrue.js', true);
await testExternal('./pluginAbsolute.js', 'absolute');
await testExternal('./pluginNormalize.js', true);
},
resolveId(source) {
if (source.endsWith('/pluginDirect.js')) return false;
if (source.endsWith('/pluginTrue.js'))
return { id: path.join(__dirname, 'pluginTrue.js'), external: true };
if (source.endsWith('/pluginAbsolute.js'))
return { id: '/pluginAbsolute.js', external: 'absolute' };
if (source.endsWith('/pluginNormalize.js'))
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'normalize' };
return { id: path.join(__dirname, 'pluginNormalize.js'), external: 'relative' };
if (source === '/absolute.js') return path.join(__dirname, 'absolute.js');
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/function/samples/options-hook/_config.js
Expand Up @@ -19,7 +19,7 @@ module.exports = {
context: 'undefined',
experimentalCacheExpiry: 10,
input: ['used'],
normalizeExternalPaths: true,
makeAbsoluteExternalsRelative: true,
perf: false,
plugins: [
{
Expand Down

0 comments on commit 72ff786

Please sign in to comment.