Skip to content

Commit

Permalink
Upgrade to Webpack 4 (#10)
Browse files Browse the repository at this point in the history
- Add a warning when the config was placed in the wrong plugin array
- Use Webpack 4 Tapable/Hooks API when possible
- Rename everything related to Webpack 3 or lower API to legacy to avoid confusion
- Add section in the readme to warn for missplacing the plugin
- Add rimraf as devDependency so that yarn build works for Windows too
- Upgrade the example to Webpack 4 latest
  • Loading branch information
Nayni authored and jonaskello committed Mar 13, 2018
1 parent 6ce72b7 commit e9b0c2a
Show file tree
Hide file tree
Showing 5 changed files with 2,876 additions and 326 deletions.
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

0 comments on commit e9b0c2a

Please sign in to comment.