TypeStrong / ts-loader

TypeScript loader for webpack
https://johnnyreilly.com/ts-loader-goes-webpack-5
MIT License
3.45k stars 428 forks source link

NormalModuleReplacementPlugin doesn't play nice with ts-loader #838

Open robertmclaws opened 6 years ago

robertmclaws commented 6 years ago

Expected Behaviour

When I implement the pattern for the NormalModuleReplacementPlugin, I expect that the ts-loader would be aware of the configuration and help swallow the compiler error so the compilation would finish.

Actual Behaviour

Because './config/config-APP_TARGET' does not actually exist, the compiler throws an error:

    ERROR in ./ClientApp/boot.ts
    Module build failed (from ./node_modules/ts-loader/index.js):
    Error: Typescript emitted no output for D:\GitHub\NMRP-Plus-TypeScript-Repro\WebPack-NMRP-Repro\ClientApp\boot.ts.
        at successLoader (D:\GitHub\NMRP-Plus-TypeScript-Repro\WebPack-NMRP-Repro\node_modules\ts-loader\dist\index.js:41:15)
        at Object.loader (D:\GitHub\NMRP-Plus-TypeScript-Repro\WebPack-NMRP-Repro\node_modules\ts-loader\dist\index.js:21:12)

    ERROR in D:\GitHub\NMRP-Plus-TypeScript-Repro\WebPack-NMRP-Repro\ClientApp\boot.ts
    ./ClientApp/boot.ts
    [tsl] ERROR in D:\GitHub\NMRP-Plus-TypeScript-Repro\WebPack-NMRP-Repro\ClientApp\boot.ts(1,20)
          TS2307: Cannot find module './config/config-APP_TARGET'.

The code executes locally anyway, but because webpack throws a failed error code at this point, automated build systems like Azure Pipelines fail.

Workaround:

You have to add a config-APP_TARGET.ts file to the config folder, with a blank exported module mirroring your actual config module, so that the compiler does not error out.

It would be really nice if this workaround was not necessary.

Steps to Reproduce the Problem

See repro below.

Location of a Minimal Repository that Demonstrates the Issue.

https://github.com/CloudNimble/NMRP-Plus-TypeScript-Repro

I hope this information helps someone who runs into the same problem, and I hope it can ultimately be fixed so that the pattern in the documentation can be implemented as-is.

Thanks!

johnnyreilly commented 6 years ago

Essentially here you're battling the TypeScript compiler. Thanks for sharing the workaround; that's helpful information.

I'm not sure what would make sense in terms of a change to ts-loader to support this more directly. We're open to PRs though... So if you have a good idea do share!

robertmclaws commented 6 years ago

Thanks for the quick response!

Well, does ts-loader have access to the webpack configuration instance? Because if so, then ts-loader could parse the files with the NMRP function and hot-swap the values before the file is actually compiled.

Another option would be for ts-loader to parse exception messages with the NMRP function, and if any of them match, then swallow the exception... because the generated code still functions.

The third option would be for me to open a ticket with the TypeScript team and link to this issue, which I will do in the morning. Really appreciate your help... have a great night!

johnnyreilly commented 6 years ago

have a great night!

Ha - it's 7am here :smile:

Well, does ts-loader have access to the webpack configuration instance?

ts-loader has access to everything that any loader has access to. I'm not certain that includes the webpack configuration instance anymore. If memory serves loaders used to have access to that prior to webpack 2. But not since (intentionally). Could be worth double checking that; I'm not 100%.

Another option would be for ts-loader to parse exception messages with the NMRP function, and if any of them match, then swallow the exception... because the generated code still functions.

Probably the easiest option to achieve I'd imagine though it feels hacky.

The third option would be for me to open a ticket with the TypeScript team and link to this issue

I think this is worth investigating..

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 5 years ago

Closing as stale. Please reopen if you'd like to work on this further.

alshdavid commented 5 years ago

Hey this seems to still be a problem

johnnyreilly commented 5 years ago

I've reopened it - I'm not sure if anyone is looking at it though

TrejGun commented 4 years ago

Is there any progress on this? i'd really like to replace babel-loader with ts-loader but broken HMR makes me sad

johnnyreilly commented 4 years ago

Me too - would you like to work on this? We welcome assistance 😊

TrejGun commented 4 years ago

I would love to assist you however I have no idea about how this stack works. but before trying to study of how it works i have couple of essential questions is it possible but nobody has time to work on this? what was the last problem ? is it impossible because of tsc behavior? if second answer is yes - do you have a channel to speak to ts team and request changes? ok this is more than couple :P

TrejGun commented 3 years ago

react is now migrating from hot reloading to fast refresh whatever it means. Will this new approach help to enable desired functionality?

TrejGun commented 3 years ago

it looks like it already works using

@pmmmwh/react-refresh-webpack-plugin react-refresh-typescript fork-ts-checker-webpack-plugin

even for monorepos

here is an example https://github.com/chanced/ts-monorepo

paviad commented 2 years ago

This is a very annoying problem (which still exists as of writing this message), who's the responsible party for fixing this do you think?

johnnyreilly commented 2 years ago

This is open source. There are no obligations. There's just potential for collaboration.

We scratch our itches. This is something that's impacting you, I completely encourage you to dig in and see if you can help! ❤️🌻

paviad commented 2 years ago

Can you provide your best guess on where to start looking?

johnnyreilly commented 2 years ago

Probably the best way is to come up with a minimal repro to test against, npm link a local build of ts-loader and start experimenting, looking particularly at the code that leads into where the error is experienced.

You might also find this helpful: https://github.com/TypeStrong/ts-loader/blob/main/CONTRIBUTING.md#debugging-ts-loader-installed-from-npm-in-your-own-webpack-project

paviad commented 2 years ago

I am not sure if my problem is the same as specified by the original poster, but I have finally - after about 10 hours of research - found a workaround:

My situation is this, I have environment specific config files with constant and type declarations. I wanted to be able to use tsc to build either environment, and also to be able to use webpack on each environment. I had achieved this finally with these steps:

  1. I had configured webpack with the NormalModuleReplacementPlugin thus:
    new NormalModuleReplacementPlugin(
    /config$/,
    `config_${env.config}`
    ),
  2. I've configured webpack resolve.alias thus:
    alias: {
    [`config_${env.config}`]: path.resolve(__dirname, `src/config_${env.config}.ts`),
    }
  3. And I've configured my tsconfig.{env}.json with some path mappings (replace {env} with the environment name):
    "baseUrl": "./src",
    "paths": {
    "*": ["*", "./*_{env}"]
    },
  4. Configured webpack's module.rules to set path mappings based on the environment file, and also to compile only bundled files:
    rules: [
    {
    test: /\.tsx?$/,
    use: [{
      loader: 'ts-loader',
      options: {
        compilerOptions: {
          paths: {
            '*': ['*', `*_${env.config}`],
          }
        },
        onlyCompileBundledFiles: true,
      },
    }],
    },
    ],
  5. And finally, I changed my source code to import this virtual module:
    import { whatever } from 'config';`

Note: A few extra points for readers not familiar with webpack - to be able to pass an arbitrary string to webpack such as the environment name in the steps above, you have to change module.exports to a function accepting an environment object:

module.exports = env => ...

Then you can invoke webpack as such webpack --env config=blabla and that object will have a config property with value blabla.

Also, if you want to be able to see declarations for a particular environment while developing, edit your tsconfig.json with the appropriate path mappings as in step 4.