Skip to content

Commit

Permalink
add extension alias
Browse files Browse the repository at this point in the history
add new option extensionAlias which maps extension to extension alias
  • Loading branch information
vankop committed Jun 27, 2022
1 parent ddc96f8 commit 9722550
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 2 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ myResolver.resolve({}, lookupStartPath, request, resolveContext, (
#### Resolver Options

| Field | Default | Description |
| ---------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|------------------|-----------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| alias | [] | A list of module alias configurations or an object which maps key to value |
| aliasFields | [] | A list of alias fields in description files |
| extensionAlias | {} | An object which maps extension to extension aliases |
| cachePredicate | function() { return true }; | A function which decides whether a request should be cached or not. An object is passed to the function with `path` and `request` properties. |
| cacheWithContext | true | If unsafe cache is enabled, includes `request.context` in the cache key |
| conditionNames | ["node"] | A list of exports field condition names |
Expand Down Expand Up @@ -142,7 +143,7 @@ enhanced-resolve will try to resolve requests containing `#` as path and as frag
## Tests

```javascript
npm test
yarn test
```

[![Build Status](https://secure.travis-ci.org/webpack/enhanced-resolve.png?branch=main)](http://travis-ci.org/webpack/enhanced-resolve)
Expand Down
62 changes: 62 additions & 0 deletions lib/ExtensionAliasPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/

"use strict";

const forEachBail = require("./forEachBail");

/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{ alias: string|string[], extension: string }} ExtensionAliasOption */

module.exports = class ExtensionAliasPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {ExtensionAliasOption} options options
* @param {string | ResolveStepHook} target target
*/
constructor(source, options, target) {
this.source = source;
this.options = options;
this.target = target;
}

/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const { extension, alias: aliasArray } = this.options;
resolver
.getHook(this.source)
.tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => {
const requestPath = request.path;
if (!requestPath || !requestPath.endsWith(extension)) return callback();
const resolve = (alias, callback) => {
resolver.doResolve(
target,
{
...request,
path: `${requestPath.slice(0, -extension.length)}${alias}`,
relativePath: request.relativePath
? `${request.relativePath.slice(0, -extension.length)}${alias}`
: request.relativePath
},
`aliased from extension alias with mapping '${extension}' to '${alias}'`,
resolveContext,
callback
);
};

if (aliasArray.length > 1) {
forEachBail(aliasArray, resolve, callback);
} else {
resolve(aliasArray[0], callback);
}
});
}
};
17 changes: 17 additions & 0 deletions lib/ResolverFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ConditionalPlugin = require("./ConditionalPlugin");
const DescriptionFilePlugin = require("./DescriptionFilePlugin");
const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
const ExportsFieldPlugin = require("./ExportsFieldPlugin");
const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
const FileExistsPlugin = require("./FileExistsPlugin");
const ImportsFieldPlugin = require("./ImportsFieldPlugin");
const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
Expand All @@ -38,19 +39,22 @@ const UnsafeCachePlugin = require("./UnsafeCachePlugin");
const UseFilePlugin = require("./UseFilePlugin");

/** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */

/** @typedef {string|string[]|false} AliasOptionNewRequest */
/** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
/** @typedef {{[k: string]: string[] }} ExtensionAliasOptions */
/** @typedef {{apply: function(Resolver): void} | function(this: Resolver, Resolver): void} Plugin */

/**
* @typedef {Object} UserResolveOptions
* @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
* @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
* @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
* @property {(string | string[])[]=} aliasFields A list of alias fields in description files
* @property {(function(ResolveRequest): boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
* @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
Expand Down Expand Up @@ -83,6 +87,7 @@ const UseFilePlugin = require("./UseFilePlugin");
* @property {AliasOptionEntry[]} alias
* @property {AliasOptionEntry[]} fallback
* @property {Set<string | string[]>} aliasFields
* @property {ExtensionAliasOption[]} extensionAlias
* @property {(function(ResolveRequest): boolean)} cachePredicate
* @property {boolean} cacheWithContext
* @property {Set<string>} conditionNames A list of exports field condition names.
Expand Down Expand Up @@ -197,6 +202,14 @@ function createOptions(options) {
: false
: options.enforceExtension,
extensions: new Set(options.extensions || [".js", ".json", ".node"]),
extensionAlias: options.extensionAlias
? Object.keys(options.extensionAlias).map(k => ({
extension: k,
alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
k
]
}))
: [],
fileSystem: options.useSyncFileSystemCalls
? new SyncAsyncFileSystemDecorator(
/** @type {SyncFileSystem} */ (
Expand Down Expand Up @@ -251,6 +264,7 @@ exports.createResolver = function (options) {
descriptionFiles,
enforceExtension,
exportsFields,
extensionAlias,
importsFields,
extensions,
fileSystem,
Expand Down Expand Up @@ -602,6 +616,9 @@ exports.createResolver = function (options) {
aliasFields.forEach(item => {
plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
});
extensionAlias.forEach(item =>
plugins.push(new ExtensionAliasPlugin("file", item, "final-file"))
);
plugins.push(new NextPlugin("file", "final-file"));

// final-file
Expand Down
81 changes: 81 additions & 0 deletions test/extension-alias.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const path = require("path");
const fs = require("fs");
const should = require("should");

const CachedInputFileSystem = require("../lib/CachedInputFileSystem");
const ResolverFactory = require("../lib/ResolverFactory");

/** @typedef {import("../lib/util/entrypoints").ImportsField} ImportsField */

describe("extension-alias", () => {
const fixture = path.resolve(__dirname, "fixtures", "extension-alias");
const nodeFileSystem = new CachedInputFileSystem(fs, 4000);

const resolver = ResolverFactory.createResolver({
extensions: [".js"],
fileSystem: nodeFileSystem,
mainFiles: ["index.js"],
extensionAlias: {
".js": [".ts", ".js"]
}
});

it("should alias fully specified file", done => {
resolver.resolve({}, fixture, "./index.js", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "index.ts"));
done();
});
});

it("should alias specified extension", done => {
resolver.resolve({}, fixture, "./dir", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "dir", "index.ts"));
done();
});
});

it("should result successfully without aliasing #1", done => {
resolver.resolve({}, fixture, "./dir2", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
done();
});
});

it("should result successfully without aliasing #2", done => {
resolver.resolve({}, fixture, "./dir2/index.js", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
done();
});
});

describe("should result successfully without alias array", () => {
const resolver = ResolverFactory.createResolver({
extensions: [".js"],
fileSystem: nodeFileSystem,
mainFiles: ["index.js"],
extensionAlias: {
".js": []
}
});

it("directory", done => {
resolver.resolve({}, fixture, "./dir2", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
done();
});
});

it("file", done => {
resolver.resolve({}, fixture, "./dir2/index.js", {}, (err, result) => {
if (err) return done(err);
should(result).be.eql(path.resolve(fixture, "dir2", "index.js"));
done();
});
});
});
});
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
13 changes: 13 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ declare class CloneBasenamePlugin {
target: any;
apply(resolver: Resolver): void;
}
declare interface ExtensionAliasOption {
alias: string | string[];
extension: string;
}
declare interface ExtensionAliasOptions {
[index: string]: string[];
}
declare interface FileSystem {
readFile: {
(arg0: string, arg1: FileSystemCallback<string | Buffer>): void;
Expand Down Expand Up @@ -214,6 +221,7 @@ declare interface ResolveOptions {
alias: AliasOption[];
fallback: AliasOption[];
aliasFields: Set<string | string[]>;
extensionAlias: ExtensionAliasOption[];
cachePredicate: (arg0: ResolveRequest) => boolean;
cacheWithContext: boolean;

Expand Down Expand Up @@ -322,6 +330,11 @@ declare interface UserResolveOptions {
*/
fallback?: AliasOptions | AliasOption[];

/**
* An object which maps extension to extension aliases
*/
extensionAlias?: ExtensionAliasOptions;

/**
* A list of alias fields in description files
*/
Expand Down

0 comments on commit 9722550

Please sign in to comment.