microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.75k stars 12.46k forks source link

Bundling typescript using webpack: the request of a dependency is an expression (+possible fix) #39436

Open AviVahl opened 4 years ago

AviVahl commented 4 years ago

TypeScript Version: 3.9.6 and 4.0.0-dev.20200706

Search Terms: webpack request of a dependency expression

Code

import ts from 'typescript';
console.log(ts)

Expected behavior: Bundle successfully, and without warnings, using webpack.

Actual behavior: Bundles successfully, but a warning is shown:

WARNING in ./node_modules/typescript/lib/typescript.js 5710:41-60
Critical dependency: the request of a dependency is an expression

Playground Link: N/A

Related Issues: Couldn't find any that talked about this issue

Suggested fix:

The warning is printed due to the following dynamic require call:

require: function (baseDir, moduleName) {
    try {
        var modulePath = ts.resolveJSModule(moduleName, baseDir, nodeSystem);
        return { module: require(modulePath), modulePath: modulePath, error: undefined };
    }
    catch (error) {
        return { module: undefined, modulePath: undefined, error: error };
    }
}

Rather than calling require directly, create a helper function:

function requireModule(requestingModule, specifier) {
    return requestingModule.require(specifier)
}

And call it the following way:

return { module: requireModule(module, modulePath), modulePath: modulePath, error: undefined };

The resulting code will behave 1:1 in Node, while not triggering any warnings in bundlers trying to resolve these dynamic calls.

antoineol commented 3 years ago

Hi,

Same thing here, when typescript is bundled with create-react-app (typescript is behind ts-morph), I have 3 warnings:

./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression

./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression

./node_modules/typescript/lib/typescript.js
Module not found: Can't resolve 'perf_hooks' in 'C:\Users\User\perso\app\nocode-front\node_modules\typescript\lib'

It seems to work on a sample code, although I can't say whether all features are here.

It can be reproduced by creating a react app (e.g. npx create-react-app my-app --template typescript) then adding the following in any of the files (e.g. index.tsx or App.tsx):

import typescript from "typescript";

console.log(typeof typescript !== 'undefined'); // To force including typescript in the bundle

Side note: when I try on codesandbox, no warning. On stackblitz, it complains that "perf_hooks" module is not found and it's blocking the bundling (installing "perf_hooks" in the dependencies doesn't solve it).

elevatebart commented 3 years ago

@RyanCavanaugh I am also interested in this. Do you think I should create a Pull Request with the suggested fix?

jtbandes commented 3 years ago

I ran into a similar issue, and worked around it by just removing the dynamic require()s from node_modules/typescript/lib/typescript.js at build time using string-replace-loader: https://github.com/foxglove/studio/pull/546

AviVahl commented 3 years ago

A cleaner workaround which we've been using for a while now (several typescript versions) is to tell webpack to avoid parsing typescript.js:

/** @type {import('webpack').Configuration} */
module.exports = {
    // ...
    module: {
        rules: [
            // ...
        ],
        noParse: [require.resolve('typescript/lib/typescript.js')],
    },
    // ...
};

Works with both webpack@4 and webpack@5. The above configuration (which uses require.resolve) assumes there's a single copy of typescript being bundled, and it's the same one available to the webpack.config.js file itself.

WilliamTepet commented 3 years ago

Angular V. 9

I had the same issue, however it was caused for this code import {toTitleCase} from "codelyzer/util/utils";

For some reason that caused the error, so I just removed it from the file where I was doing the import and the project compiled without any errors/warnings.

Just in case anybody else had the same problem.

replaysMike commented 2 years ago

I've tried @AviVahl 's recommended solution but it doesn't seem to do anything. In my case I'm using react-app-rewired with the following:

config-overrides.js

const webpack = require('webpack');
module.exports = function override(config, env) {
  // ....
  config.plugins.push(
    new webpack.ProvidePlugin({
      process: 'process/browser',
      Buffer: ['buffer', 'Buffer'],
    }),
  );
  config.module.noParse = [require.resolve('typescript/lib/typescript.js')];
  return config;
}

I still get the following error on build and haven't found a working solution yet, maybe I'm doing something wrong.

Module not found: Error: Can't resolve 'perf_hooks' in 'node_modules\typescript\lib'

Critical dependency: the request of a dependency is an expression
AviVahl commented 2 years ago

@replaysMike according to https://github.com/timarney/react-app-rewired#1-webpack-configuration---development--production

It is also not able to be used to customise the Webpack Dev Server that is used to serve pages in development mode because create-react-app generates a separate Webpack configuration for use with the dev server using different functions and defaults.

EDIT: oh, you wrote "on build", so it might be something else. Needless to say, we're still using the workaround on my team and everything works. Though our webpack config is not abstracted away by some third-party...

replaysMike commented 2 years ago

@AviVahl ty. That's why I was using react-app-rewired in order to customize webpack 5 via config-overrides.js. I don't know if this is recommended, but I was able to solve it just now with:

// config-overrides.js
module.exports = function override(config, env) {
  // ...
  config.module.noParse = /typescript/;
  config.ignoreWarnings = [/Failed to parse source map/];
}

It seems webpack noParse ignored the path as a string and required an expression - perhaps this is a new change with webpack 5?

AviVahl commented 2 years ago

We're using it with webpack5. Perhaps you're on windows and experiencing C: vs c:? If launching from vscode, it messes up the drive letter (forces it to be lowercased)

replaysMike commented 2 years ago

hmm that's a possibility. require.resolve() was resolving it to: B:\\gitrepo\\personalcode\\Binner\\Binner\\Binner.Web\\ClientApp\\node_modules\\typescript\\lib\\typescript.js

and the error was showing "Can't resolve 'perf_hooks' in": B:\gitrepo\personalcode\Binner\Binner\Binner.Web\ClientApp\node_modules\typescript\lib

Perhaps internally it's not doing the comparison using double slashes?

AviVahl commented 2 years ago

require.resolve returns absolute paths, so on Windows it uses just a single slash as separator. If anything stringifies that absolute path, the separators will show up as escaped slash (double slash). Perhaps react-rewired is doing something to that string when merging configs?

Another possibility is that you've inspected two different contexts: one while debugging and seeing the slash as escaped, and another seeing a printed error by webpack, where the slash will show as single.

To better understand what's going wrong, I'd pass a function to noParse and compare the paths that come in to the result of require.resolve: https://webpack.js.org/configuration/module/#modulenoparse

Add the following somewhere in the webpack config:

console.log(require.resolve("typescript/lib/typescript.js"));

and:

noParse: content => {console.log(content); return false;}

Because it's Windows, I'd bet it has to do with case insensitivity of paths.

disambiguator commented 2 years ago

To add to this, I tried the solution described in https://github.com/microsoft/TypeScript/issues/39436#issuecomment-817029140, and while it did make the warnings go away, it also completely squashed all output coming from webpack.

I.e. I no longer saw messages like

Version: webpack 4.46.0
Time: 91795ms
Built at: 07/28/2022 2:54:31 PM
                                                   Asset      Size                                             Chunks                                Chunk Names
                                                    0.js   702 KiB                                                  0  [emitted]              [big]  
                                                    1.js   321 KiB                                                  1  [emitted]              [big]  
                                                   10.js  6.09 KiB                                                 10  [emitted] 

and instead just

webpack built 87d9467409f36894a95e in 91766ms

Hopefully this gets fixed internally to TypeScript.

michalczerwinski commented 1 year ago

I have the same issue in recent create-react-app, unfortunately any of the above doesn't work. I use react-app-rewired, webpack 5 and config-overrides.js

Any chance we can introduce the suggestion from @AviVahl ?

wrightwriter commented 1 year ago

@replaysMike 's solution works for me in current webpack/ts/etc versions!

yoshikiohshima commented 1 year ago

I am trying to compile typescript code in the browser and stumble upon this problem. It works in a simple isolated test case I wrote works, but when I move it to a larger production code it fails because of this "can't resolve 'perf_hooks'" error, and adding the noParse rule does not solve it (it rather moves the error from the bundle time to the run time). What is the solution? My current solution is to load the package from A CDN and that works... But I'd rather resolve it at the bundlign time. https://github.com/croquet/microverse/blob/ac3a5412ad6b4ec860e209bec9836fd9f33cf2e8/prelude.js#L63

jakebailey commented 1 year ago

Didn't realize this issue existed; one thing you may try is to just use an IgnorePlugin to bulk ignore all require calls; if you're bundling for the browser they won't be used anyway.

I have not tested, but something like:

new webpack.IgnorePlugin({
  resourceRegExp: /.*/,
  contextRegExp: /typescript.js$/,
});

Though if you have an easy repro I'd be happy to play around with it.

yoshikiohshima commented 1 year ago

Thanks for a reply. This IgnorePlugin does not work for me (I tried some different RegExps). Yes, I'll try to make a smaller repro... but that has been hard. I can make a branch on the above microverse repo that changes the script line with import to test it, if you are willing to try it in that form.

yoshikiohshima commented 1 year ago

Didn't realize this issue existed; one thing you may try is to just use an IgnorePlugin to bulk ignore all require calls; if you're bundling for the browser they won't be used anyway.

I have not tested, but something like:

new webpack.IgnorePlugin({
  resourceRegExp: /.*/,
  contextRegExp: /typescript.js$/,
});

Though if you have an easy repro I'd be happy to play around with it.

This branch, which is not an isolated test case though, shows the issue: https://github.com/croquet/microverse/tree/ts-import-test

jakebailey commented 1 year ago

@yoshikiohshima I finally had time to look at the above; the issue here comes down to the fact that our package.json doesn't include perf_hooks in the browser section of package.json, which was unintentional. I've sent #56062 for that.

yoshikiohshima commented 1 year ago

thanks!

curran commented 11 months ago

FWIW there's a similar warning that occurs when bundling for the browser using Vite. It looks like this:

> vite build

vite v4.5.0 building for production...
[plugin:vite:resolve] Module "perf_hooks" has been externalized for browser compatibility, imported by
"/home/curran/repos/vzcode/node_modules/typescript/lib/typescript.js". See 
http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility 
for more details.

Fingers crossed that the changes in https://github.com/microsoft/TypeScript/pull/56062 solve this issue as well! Thank you for the fix.

jakebailey commented 11 months ago

It definitely should; you can try out the RC or nightly.