Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to Webpack 4 #10

Merged
merged 1 commit into from
Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ module.exports = {
}
```

> Notice that the plugin is placed in the `resolve.plugins` section of the configuration. `tsconfig-paths-webpack-plugin` is a resolve plugin and should only be placed in this part of the configuration. Don't confuse this with the plugins array at the root of the webpack configuration object.

## Options

#### configFile _(string) (default='tsconfig.json')_
Expand Down
1 change: 1 addition & 0 deletions example/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require("path");
const TsconfigPathsPlugin = require("../index");

module.exports = {
mode: "development",
context: path.resolve(__dirname, "src"),
entry: "./index",
output: {
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@
"@types/node": "^8.0.9",
"husky": "^0.14.3",
"lint-staged": "^6.0.0",
"rimraf": "^2.6.2",
"shelljs": "^0.7.8",
"ts-loader": "^2.2.2",
"ts-node": "^3.2.0",
"tslint": "^5.8.0",
"tslint-immutable": "^4.4.0",
"typescript": "^2.4.1",
"webpack": "^3.1.0"
"webpack": "^4.1.1",
"webpack-cli": "^2.0.11"
},
"scripts": {
"precommit": "lint-staged",
"compile": "tsc -p src",
"compile:tests": "tsc -p tests",
"compile:example": "tsc -p example",
"example": "yarn build && cd example && webpack",
"build": "rm -rf lib && tsc -p src",
"build": "rimraf lib && tsc -p src",
"lint":
"tslint -t msbuild './src/**/*.ts{,x}' -e './src/node_modules/**/*'",
"publish:major": "yarn run build && node scripts/publish.js major",
Expand Down
190 changes: 166 additions & 24 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,28 @@ interface ResolverPlugin {

interface Resolver {
readonly apply: (plugin: ResolverPlugin) => void;
readonly plugin: (source: string, cb: ResolverCallback) => void;
readonly doResolve: (
target: string,
req: Request,
desc: string,
callback: Callback
) => void;
readonly plugin: (source: string, cb: ResolverCallbackLegacy) => void;
readonly doResolve: doResolveLegacy | doResolve;
readonly join: (relativePath: string, innerRequest: Request) => Request;
// readonly extensions: ReadonlyArray<string>;
readonly fileSystem: ResolverFileSystem;
readonly getHook: (hook: string) => Tapable;
}

type doResolveLegacy = (
target: string,
req: Request,
desc: string,
callback: Callback
) => void;

type doResolve = (
hook: Tapable,
req: Request,
message: string,
resolveContext: ResolveContext,
callback: Callback
) => void;

interface ResolverFileSystem {
readonly stat: (
path: string,
Expand Down Expand Up @@ -51,14 +61,47 @@ interface ResolverFileSystem {
readonly readJsonSync: (path: string) => {};
}

type ResolverCallback = (request: Request, callback: Callback) => void;
interface ResolveContext {
log?: string;
stack?: string;
missing?: string;
}

interface Tapable {
readonly tapAsync: (
options: TapableOptions,
callback: ResolverCallback
) => Promise<void>;
}

interface TapableOptions {
readonly name: string;
}

type ResolverCallbackLegacy = (request: Request, callback: Callback) => void;
type ResolverCallback = (
request: Request,
resolveContext: ResolveContext,
callback: Callback
) => void;

type CreateInnerCallback = (
callback: Callback,
options: Callback,
message?: string,
messageOptional?: string
) => Callback;

type CreateInnerContext = (
options: {
log?: string;
stack?: string;
missing?: string;
},
message?: string,
messageOptional?: string
) => ResolveContext;

type getInnerRequest = (resolver: Resolver, request: Request) => string;

interface Request {
Expand All @@ -77,7 +120,6 @@ interface Callback {
missing?: string;
}

const createInnerCallback: CreateInnerCallback = require("enhanced-resolve/lib/createInnerCallback");
const getInnerRequest: getInnerRequest = require("enhanced-resolve/lib/getInnerRequest");

export class TsconfigPathsPlugin implements ResolverPlugin {
Expand Down Expand Up @@ -131,31 +173,126 @@ export class TsconfigPathsPlugin implements ResolverPlugin {
if (!baseUrl) {
// Nothing to do if there is no baseUrl
this.log.logWarning(
"Found no baseUrl in tsconfig.json, not applying tsconfig-paths-webpack-plugin"
"tsconfig-paths-webpack-plugin: Found no baseUrl in tsconfig.json, not applying tsconfig-paths-webpack-plugin"
);
return;
}

resolver.plugin(
this.source,
createPlugin(
this.matchPath,
resolver,
this.absoluteBaseUrl,
this.target,
this.extensions
)
);
// The file system only exists when the plugin is in the resolve context. This means it's also properly placed in the resolve.plugins array.
// If not, we should warn the user that this plugin should be placed in resolve.plugins and not the plugins array of the root config for example.
// This should hopefully prevent issues like: https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/9
if (!resolver.fileSystem) {
this.log.logWarning(
"tsconfig-paths-webpack-plugin: No file system found on resolver." +
" Please make sure you've placed the plugin in the correct part of the configuration." +
" This plugin is a resolver plugin and should be placed in the resolve part of the Webpack configuration."
);
return;
}

// getHook will only exist in Webpack 4, if so we should comply to the Webpack 4 plugin system.
if (resolver.getHook && typeof resolver.getHook === "function") {
resolver
.getHook(this.source)
.tapAsync(
{ name: "TsconfigPathsPlugin" },
createPluginCallback(
this.matchPath,
resolver,
this.absoluteBaseUrl,
resolver.getHook(this.target),
this.extensions
)
);
} else {
// This is the legacy (Webpack < 4.0.0) way of using the plugin system.
resolver.plugin(
this.source,
createPluginLegacy(
this.matchPath,
resolver,
this.absoluteBaseUrl,
this.target,
this.extensions
)
);
}
}
}

function createPlugin(
function createPluginCallback(
matchPath: TsconfigPaths.MatchPathAsync,
resolver: Resolver,
absoluteBaseUrl: string,
target: string,
hook: Tapable,
extensions: ReadonlyArray<string>
): ResolverCallback {
const fileExistAsync = createFileExistAsync(resolver.fileSystem);
const readJsonAsync = createReadJsonAsync(resolver.fileSystem);
return (
request: Request,
resolveContext: ResolveContext,
callback: Callback
) => {
const innerRequest = getInnerRequest(resolver, request);

if (
!innerRequest ||
(innerRequest.startsWith(".") || innerRequest.startsWith(".."))
) {
return callback();
}

matchPath(
innerRequest,
readJsonAsync,
fileExistAsync,
extensions,
(err, foundMatch) => {
if (err) {
callback(err);
}

if (!foundMatch) {
return callback();
}

const newRequest = {
...request,
request: foundMatch,
path: absoluteBaseUrl
};

// Only at this point we are sure we are dealing with the latest Webpack version (>= 4.0.0)
// So only now can we require the createInnerContext function.
// (It doesn't exist in legacy versions)
const createInnerContext: CreateInnerContext = require("enhanced-resolve/lib/createInnerContext");

return (resolver.doResolve as doResolve)(
hook,
newRequest,
`Resolved request '${innerRequest}' to '${foundMatch}' using tsconfig.json paths mapping`,
createInnerContext({ ...resolveContext }),
(err2: Error, result2: string): void => {
if (arguments.length > 0) {
return callback(err2, result2);
}
// don't allow other aliasing or raw request
callback(null, null);
}
);
}
);
};
}

function createPluginLegacy(
matchPath: TsconfigPaths.MatchPathAsync,
resolver: Resolver,
absoluteBaseUrl: string,
target: string,
extensions: ReadonlyArray<string>
): ResolverCallbackLegacy {
const fileExistAsync = createFileExistAsync(resolver.fileSystem);
const readJsonAsync = createReadJsonAsync(resolver.fileSystem);
return (request, callback) => {
Expand Down Expand Up @@ -188,7 +325,12 @@ function createPlugin(
path: absoluteBaseUrl
};

return resolver.doResolve(
// Only at this point we are sure we are dealing with a legacy Webpack version (< 4.0.0)
// So only now can we require the createInnerCallback function.
// (It's already deprecated and might be removed down the line).
const createInnerCallback: CreateInnerCallback = require("enhanced-resolve/lib/createInnerCallback");

return (resolver.doResolve as doResolveLegacy)(
target,
newRequest,
`Resolved request '${innerRequest}' to '${foundMatch}' using tsconfig.json paths mapping`,
Expand Down
Loading