webpack-contrib / file-loader

File Loader
MIT License
1.86k stars 257 forks source link

2 files emitted if file-loader query and rule used together #336

Closed MiniGod closed 4 years ago

MiniGod commented 5 years ago

Expected Behavior

The module.exports generated for the require should always be the path to the file with the content, not a file that exports the path to said file.

Actual Behavior

When a file is loaded with require('file-loader!./b.js'), as well as configured in webpack.config.js, two files are being emitted from file-loader. This results in using the wrong file path to the content.

The files:

  1. dist/9fd622c116f2bb83d54eae2c49d8141e.js A file that module.exports the filename of the 2nd file.
  2. dist/lib/f588e2144d0ba4ab157205ce359e6a0a.js The actual file.

File tree:

dist
├── 9fd622c116f2bb83d54eae2c49d8141e.js
├── lib
│   └── f588e2144d0ba4ab157205ce359e6a0a.js
└── main.js

The module.exports generated for the require (inside dist/main.js) is the path to the 1st file. This is not the file with the content. Content of dist/9fd622c116f2bb83d54eae2c49d8141e.js:

module.exports = __webpack_public_path__ + "lib/f588e2144d0ba4ab157205ce359e6a0a.js";

Code

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\/b\.js$/,
        use: [
          {
            loader: "file-loader",
            options: {
              outputPath: "lib/"
            }
          }
        ]
      }
    ]
  },

  entry: "./a.js"
};
// a.js
ace.config.setModuleUrl("ace/ext/beautify", require("file-loader!./b.js"));

How Do We Reproduce?

https://gist.github.com/MiniGod/2e4615fe2ca6762513a8acd50a59bad7

Notice a.js is doing require("file-loader!./b.js"), and file-loader is also configured in webpack.config.js for b.js in an attempt to customize the outputPath for b.js.

Background

I was trying to use ace-builds, and specifically ace-builds/webpack-resolver, and configure the output path. I've since gone with a different approach, but this still seems like a bug.

alexander-akait commented 5 years ago

Your configuration provide expected output, i can't understand why you expected other behavior and say what it is bug :confused: Describe your use case and why you need this logic.

MiniGod commented 5 years ago

Thanks for the fast response!

I was trying to configure the outputPath for the files that ace-builds/webpack-resolver pulls in. They all end up in the root of dist/ which I did not want.

See: https://github.com/ajaxorg/ace-builds/blob/master/webpack-resolver.js

I might very well be doing this the wrong way.

alexander-akait commented 5 years ago

@MiniGod hm, can you provide full reproducible repo to investigate problem?

alexander-akait commented 5 years ago

You expected what ace.config.setModuleUrl("ace/ext/beautify", require("file-loader!./b.js")); => ace.config.setModuleUrl("ace/ext/beautify", ace.define(/* foo bar */));, right?

Input:

ace.config.setModuleUrl("ace/ext/beautify", require("file-loader!./b.js"));`

Output:

ace.config.setModuleUrl("ace/ext/beautify", __webpack_public_path__ + "lib/7e18d1cdb1e87c1556053fa0b9bf7a98.js");

right?

MiniGod commented 5 years ago

Yes that's the output that I'd expect. However, the output in dist is like this:

ace.config.setModuleUrl("ace/ext/beautify", __webpack_public_path__ + "9fd622c116f2bb83d54eae2c49d8141e.js");

And the content of dist/9fd622c116f2bb83d54eae2c49d8141e.js is:

module.exports = __webpack_public_path__ + "lib/f588e2144d0ba4ab157205ce359e6a0a.js";

So ace tries to load that file as the js file defining said module, which errors with something along the lines of "module is not defined".

If you still want me to create a full reproducible repo, let me know.

alexander-akait commented 5 years ago

Just for information, what is use case, why you need put files in lib?

MiniGod commented 5 years ago

The goal is that the public path to the files from ace will be something like: https://hostname/admin/lib/ace/f588e2144d0ba4ab157205ce359e6a0a.js

Webpack builds an SPA and I have the public path set to /admin/. Since ace spits out 400 files I don't want those directly inside of the admin folder.

alexander-akait commented 5 years ago

:+1: thanks, i will look on it in near future

austin880625 commented 4 years ago

Same issue happens for importing images(SVG as far as I tested). If the returned public path is used in HTML attributes like src. the browser will fetch the string like module.exports = __webpack_public_path__ ... and fail to display the image.

This also happens when rules of loaders testing over a same file(e.g. an svg can be an image or a font, and hence may be included in separate rules with different name or outputPath options. This is the case I encountered).

I think this is due to the emitFile interface of webpack instead of file-loader. Such behaviour is reasonable for avoiding same dependencies with duplicate js sources, but not for static assets. Since I am still not very familiar with webpack, please let me know if there are more appropriate approaches for my use case(I think matching rules on different path is enough currently?)

alexander-akait commented 4 years ago

Here interesting case:

@austin880625 I don't know what do you want to achieve but if you create a reproducible test repo with simple readme I will help you, thanks

austin880625 commented 4 years ago

@evilebottnawi I made the repository here. I currently consider this as a configuration problem. But I am still confused about how does webpack use loaders to generate the bundles, so below might not really relate to this problem but I will appreciate it if there is an answer.

I know that a loader is a functions that returns a JS module by definition. But I thought webpack will make the bundled js contain these output modules at the end. So there are two possibilities about what these modules do:

  1. The module exports content of processed files in formats that can be contained in JS like string, object, or function. For example, a CSS loader may return something like export default "h1{color:red;}" and HTML loader can transform <link href="./style.css"> into JS code that inserts <style>h1{color:red;}</style>

  2. The module just exports the public path of the processed file and emits(maybe copy but as you said that's not what this module does) the file into the output path. In previous example, the CSS loader return things like export default "/dist/css/style.css" and the link tag will be transformed into <link href="/dist/css/style.css">, or the HTML loader will try to load the content of /dist/css/style.css and then insert it into <style> tag

Because of the existences of name, outputPath and publicPath options in most loaders, the second case makes more sense. However in CSS example, the content of /dist/css/style.css can never be module.exports = __webpack_public_path__ + "css/style.css" but h1{color:red;}, and this happens in SVG files in the given repository. So should the loaders always be aware that the content inside the public path may not be the actual processed file content? or does it simply mean that the same file cannot be passed to a same loader(by either require in this original issue or being covered by different config rules with different name or outputPath options)?

alexander-akait commented 4 years ago

@austin880625 Sorry for delay, if you need content of SVG file you need to use raw-loader instead file-loader, file-loader emit file and return URL to an asset, I see what you want to achieve - get source of the asset and URL to asset, but it is bad idea, in this case you will get big bundle, because we should keep the source of the asset in bundle (as string) and emit the asset with the same source on disk (so you will have the source of the asset twice - first on disk, second in bundle). If you still want it, you can write own loader using pitch phase and reexport source of the asset.

alexander-akait commented 4 years ago

For original issue you need to use require("!file-loader!./b.js") (! disable normal loaders) because you pass file twice to file-loader