TypeStrong / TypeStrong/ts-loader

Ts-loader never uses enhanced-resolve as of enhanced-resolve v5.3.1

Open
#1,492 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
3.5k
Forks
439
Avg merge
17h 17m
Merged PRs (30d)
2

Description

When resolving modules attempts are first made to use enhanced-resolver (which is also used by webpack), and if that fails we fallback to the typescript resolver (src/servicesHost.ts:resolveModule ). In doing this undefined is passed as the context to the enhanced-resolver's resolveSync method.

Prior to version 5.3.1 the context value was not validated, as opposed to v5.3.1 onward (see https://github.com/webpack/enhanced-resolve/pull/261), of which the first check is to see if it's an object or not. This results in a error being throw, of which this is handled silently (see https://github.com/TypeStrong/ts-loader/blob/b4b036325b4cd97fab8b3f01fc7bc849373839d4/src/servicesHost.ts#L1270)

This means enhanced-resolve is never used, of which this is hard to pick up as we handle all error silently and fallback to the typescript resolver.

Expected Behaviour

To use enhanced-resolve when resolving modules.

Actual Behaviour

The enhanced resolver is never used when resolving modules, and falls back to the typescript resolver.

Steps to Reproduce the Problem

Try to use a custom fileSystem, by specifying this in your webpack's resolve options. Rather than this option being passed on to the enhanced-resolver , it is neglected, meaning that ts-loader falls back to the default/standard typescript filesystem. This is highlighted by the snippet code below (or in the example project below)

webpack.config.js

const fs = require("fs")
const path = require("path")
const memoryFS = require('memory-fs');
const unionfs = require("unionfs").ufs

var mfs = new memoryFS();
mfs.mkdirpSync(__dirname)
mfs.writeFileSync(path.join(__dirname,'fileSystemType.ts'), `export default () => {
  return "Using memory filesystem"
}`);

const updatedFs = unionfs.use(fs).use(mfs)

module.exports = {
  entry: "./index.ts",
  output: {
    filename: 'main.js'
  },
  target: "node",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx"],
    fileSystem: updatedFs
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "ts-loader" }
    ]
  },
  plugins: [
    new class {
     apply(compiler){
       compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
         compiler.inputFileSystem = updatedFs
       });
     }
    }
 ]
};


index.ts

import filesystemType from "./fileSystemType";
console.log(filesystemType())

Expected Output (running npx webpack and node dist/main.js)
"Using memory fileSystem"

Actual Output (running npx webpack)
TS2307: Cannot find module './fileSystemType' or its corresponding type declarations.

Location of a Minimal Repository that Demonstrates the Issue.

https://github.com/walisc/ts-loader-enhance-resolver-bug

Proposed Fix

Passing an empty object as opposed to undefined, to resolveSync. In addition, it would be good to distinguish between configuration errors and actual resolve errors. In the snippet below I do this through a resolver plugin. Do note, this is just a poc, and maybe more work /cleaning up might be required.

diff -ur ts-loader-enhance-resolver-bug/node_modules/ts-loader/dist/resolver.js ts-loader/node_modules/ts-loader/dist/resolver.js
--- ts-loader-enhance-resolver-bug/node_modules/ts-loader/dist/resolver.js	2022-08-13 09:21:51.220999000 +0200
+++ ts-loader/node_modules/ts-loader/dist/resolver.js	2022-08-14 00:59:11.408463176 +0200
@@ -2,8 +2,27 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.makeResolver = void 0;
 const enhanced_resolve_1 = require("enhanced-resolve");
+
+ class ResolveStatusPlugin {
+    setResolveSync(resolveSync){
+        this.resolveSync = resolveSync
+    }
+    apply(resolver){
+        resolver
+            .getHook("noResolve")
+            .tap("resolveStatusPlugin", (obj, error) => {               
+                this.resolveSync["resolveStatus"] = "failed"
+            });
+    }
+}
 function makeResolver(options) {
-    return enhanced_resolve_1.create.sync(options.resolve);
+    const resolveStatusPlugin = new ResolveStatusPlugin()
+    options.resolve["plugins"] = "plugins" in options.resolve ? [...plugins, resolveStatusPlugin] : [resolveStatusPlugin]
+
+    const resolveSync = enhanced_resolve_1.create.sync(options.resolve);
+    resolveStatusPlugin.setResolveSync(resolveSync)
+    return resolveSync
+
 }
 exports.makeResolver = makeResolver;
 //# sourceMappingURL=resolver.js.map
\ No newline at end of file
diff -ur ts-loader-enhance-resolver-bug/node_modules/ts-loader/dist/servicesHost.js ts-loader/node_modules/ts-loader/dist/servicesHost.js
--- ts-loader-enhance-resolver-bug/node_modules/ts-loader/dist/servicesHost.js	2022-08-13 09:21:51.220999000 +0200
+++ ts-loader/node_modules/ts-loader/dist/servicesHost.js	2022-08-14 00:59:51.609128561 +0200
@@ -732,7 +732,7 @@
 function resolveModule(resolveSync, resolveModuleName, appendTsTsxSuffixesIfRequired, scriptRegex, moduleName, containingFile, redirectedReference) {
     let resolutionResult;
     try {
-        const originalFileName = resolveSync(undefined, path.normalize(path.dirname(containingFile)), moduleName);
+        const originalFileName = resolveSync({}, path.normalize(path.dirname(containingFile)), moduleName);
         if (originalFileName) {
             const resolvedFileName = appendTsTsxSuffixesIfRequired(originalFileName);
             if (resolvedFileName.match(scriptRegex) !== null) {
@@ -740,7 +740,11 @@
             }
         }
     }
-    catch (e) { }
+    catch (e) { 
+        if (resolveSync.resolveStatus != "failed"){
+            throw e
+        }
+    }
     const tsResolution = resolveModuleName(moduleName, containingFile, redirectedReference);
     if (tsResolution.resolvedModule !== undefined) {
         const resolvedFileName = path.normalize(tsResolution.resolvedModule.resolvedFileName);

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/servicesHost.ts at resolveModule and inspect how the enhanced-resolve resolver is called and how errors are handled. Reproduce the issue with the linked minimal repository and its custom fileSystem configuration. Done means enhanced-resolve receives the configured filesystem and module resolution no longer silently bypasses it.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, webpack
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.