webpack-contrib / less-loader

Compiles Less to CSS
MIT License
957 stars 193 forks source link

less-loader is trying to resolve url references relative to the starting point(root less file) instead of using the current file #76

Closed ravipatipushkar closed 4 years ago

ravipatipushkar commented 8 years ago

I am getting the below error when trying to use the less loader with webpack to compile my less files.

ERROR in ./~/css-loader?sourceMap!./~/less-loader?relativeUrls&noIeCompat!./app/styles/app.less
    Module not found: Error: Cannot resolve 'file' or 'directory' ./fonts/randomfont.eot in C:\test\app\styles
     @ ./~/css-loader?sourceMap!./~/less-loader?relativeUrls&noIeCompat!./app/styles/app.less 6:92-125

Following is my directory structure

---Test
    |---App
    |     |---styles
    |     |       |---fontImport
    |     |       |      |---randomfont.eot
    |     |       |---app.less
    |     |---  app.js             
    |---webpack.config.js

Following are the contents of app.js require('./styles/app.less');

app.less

@import "./fontImport/fontimports.less";

body{
  background: #fafafa;
  color: #212121;
  padding: 0;
  margin: 30;
  font-size: 60px;
}

fontImports.less

@font-face {
    font-family: 'Times New Roman';
    // Does not work
        src: ~"url('./fonts/randomfont.eot')";
    // Works
    //src: ~"url('./fontImport/fonts/randomfont.eot')";
    font-weight: normal;
    font-style: normal
}

I am using the below webpack config

var path = require('path');
var extractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: './app/app.js',
    devtool: 'sourcemap',
    output: {
        filename: 'test.js',
        library: 'testlib',
        libraryTarget: 'var',
        path: './Content/Scripts',
        pathinfo: true
    },
    resolve: {
        extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js']
    },
    module: {
        loaders: [
            { test: /\.css$/, loader: "style!css" },
            {
                test: /\.less/,
                loader: extractTextPlugin.extract(
                    "css?sourceMap!" +
                    "less?relativeUrls&noIeCompat"
                    )
            },
            { test: /\.(eot|woff|woff2|ttf|svg|png|jpg)$/, loader: 'file?name=app/generated/[name].[ext]' },
        ]
    },
    plugins: [
        new extractTextPlugin("../css/styles.css"),
    ]
}

In fontImports.less, If I provide relative path of the font form fontImports.less, webpack command is failing with the below error.

Hash: 1c54ca6132cc3c5db535
Version: webpack 1.12.13
Time: 1313ms
      Asset     Size  Chunks             Chunk Names
    test.js  4.68 kB       0  [emitted]  main
test.js.map  5.28 kB       0  [emitted]  main
   [0] ./app/app.js 31 bytes {0} [built]
    + 3 hidden modules

ERROR in ./app/styles/app.less
Module not found: Error: Cannot resolve 'file' or 'directory' ./fonts/randomfont.eot in C:\test\app\styles
 @ ./app/styles/app.less 6:90-123

ERROR in ./app/styles/app.less
Module build failed: ModuleNotFoundError: Module not found: Error: Cannot resolve 'file' or 'directory' ./fonts/randomfont.eot in C:\test\app\styles
    at C:\test\node_modules\webpack\lib\Compilation.js:229:38
    at onDoneResolving (C:\test\node_modules\webpack\lib\NormalModuleFactory.js:29:20)
    at C:\test\node_modules\webpack\lib\NormalModuleFactory.js:85:20
    at C:\test\node_modules\async\lib\async.js:726:13
    at C:\test\node_modules\async\lib\async.js:52:16
    at done (C:\test\node_modules\async\lib\async.js:241:17)
    at C:\test\node_modules\async\lib\async.js:44:16
    at C:\test\node_modules\async\lib\async.js:723:17
    at C:\test\node_modules\async\lib\async.js:167:37
    at C:\test\node_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:24:19
    at onResolved (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:38:18)
    at C:\test\node_modules\enhanced-resolve\lib\Resolver.js:127:10
    at C:\test\node_modules\enhanced-resolve\lib\Resolver.js:191:15
    at applyPluginsParallelBailResult.createInnerCallback.log (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:110:4)
    at loggingCallbackWrapper (C:\test\node_modules\enhanced-resolve\lib\createInnerCallback.js:21:19)
    at C:\test\node_modules\tapable\lib\Tapable.js:134:6
    at Tapable.<anonymous> (C:\test\node_modules\enhanced-resolve\lib\DirectoryDefaultFilePlugin.js:21:12)
    at Storage.provide (C:\test\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:52:20)
    at CachedInputFileSystem.stat (C:\test\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:132:20)
    at Tapable.<anonymous> (C:\test\node_modules\enhanced-resolve\lib\DirectoryDefaultFilePlugin.js:18:6)
    at Tapable.applyPluginsParallelBailResult (C:\test\node_modules\tapable\lib\Tapable.js:139:14)
    at Tapable.<anonymous> (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:103:8)
    at Tapable.Resolver.forEachBail (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:196:3)
    at Tapable.doResolve (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:102:7)
    at Tapable.resolve (C:\test\node_modules\enhanced-resolve\lib\Resolver.js:45:14)
    at Tapable.resolve (C:\test\node_modules\enhanced-resolve\lib\UnsafeCachePlugin.js:23:14)
    at C:\test\node_modules\webpack\lib\NormalModuleFactory.js:82:29
    at C:\test\node_modules\async\lib\async.js:718:13
    at async.forEachOf.async.eachOf (C:\test\node_modules\async\lib\async.js:233:13)
    at _parallel (C:\test\node_modules\async\lib\async.js:717:9)
Child extract-text-webpack-plugin:
        + 3 hidden modules

    ERROR in ./~/css-loader?sourceMap!./~/less-loader?relativeUrls&noIeCompat!./app/styles/app.less
    Module not found: Error: Cannot resolve 'file' or 'directory' ./fonts/randomfont.eot in C:\test\app\styles
     @ ./~/css-loader?sourceMap!./~/less-loader?relativeUrls&noIeCompat!./app/styles/app.less 6:90-123

If I provide the relative path from app.less, I am getting the expected result. Do I have to include any specific configuration to make the above work ?

You can repro this issue using the sample here

duro commented 8 years ago

I can confirm this is a problem. I am running into the exact same issue.

oller commented 8 years ago

I'm encountering this problem too, particularly when trying to import the .less flag library from the node_module https://github.com/lipis/flag-icon-css

In my less file for that module

@import "~flag-icon-css/less/flag-icon.less";

This resolves fine, and webpack starts importing this file. The problem is then within the flag node package, the relative paths to the individual flag svg files ../flags/1x1/es.svg look like they're being resolved relative to starting point of the less import into webpack, i.e. styles/app.less and not relative to the node_modules package.

The error, excuse all the loaders...

    ERROR in ./~/css-loader?sourceMap!./~/postcss-loader!./~/csslint-loader!./~/less-loader?sourceMap!./app/styles/profiler.less
    Module not found: Error: Cannot resolve 'file' or 'directory' ../flags/1x1/vg.svg in /app/styles
     @ ./~/css-loader?sourceMap!./~/postcss-loader!./~/csslint-loader!./~/less-loader?sourceMap!./app/styles/app.less 6:116980-117010
jhnns commented 8 years ago

This looks like the relativeUrls option is not applied. The less-loader usually applies it automatically, thus it should work out of the box. Could you remove the relativeUrls option in your loader config and see if it works?

developit commented 8 years ago

@jhnns Tried with relativeUrls true or false, same issue. This seems related to the way the file manager provided by the dynamically injected less plugin uses the shared context from less-loader to resolve paths for url() references within all nested files. I'm not familiar enough with Webpack's internals to know how to split this out.

Is there a way to re-apply less-loader to each new less file encountered?

jhnns commented 8 years ago

We need a small test-case that demonstrates this specific bug. We do have tests that this feature works under "normal" circumstances (I'm using the less-loader also in production and it works)

developit commented 8 years ago

@jhnns Yes, I have used less-loader many times without issue. I ran into this when (intentionally) attempting to apply less-loader to modules in node_modules. In particular, it happened when using namespaced NPM module names (@foo/bar).

That being said, I was actually able to fix my issue by simply dropping the relative filepath altogether. It still bundles the font properly via webpack, but seems to circumvent the path normalization based on original filename somehow.

src: url('./icons/foo.woff') format('woff');
src: url('/icons/foo.woff') format('woff');
emmostrom commented 8 years ago

I ran into this issue because the way I set up my webpack.config.js file - the paths were getting a mixture of forward and backwards slashes (context: __dirname + '/src/main/jsx').

Changed to use path.join and path.normalize

context: path.join(dirname, '/src/main/jsx'), ... output: { path: dirname, filename: path.normalize('/src/main/webapp/js/app.js') }, ... new ExtractTextPlugin(path.normalize('src/main/webapp/css/app.css'))

This stopped errors but no files were generated so also had to update to node 4.4.4

tszymanek commented 8 years ago

I had a similar issue and solved it in a hackish way:

http://stackoverflow.com/a/37293335/1874624

edit: updated my solution

SplicePHP commented 8 years ago

I found the problem. Since most less and scss files are stuck in other directories than where they build to, the css parser gets confused. I added an attribute to the css loader named rel to specify where the file would built to. Here is the modified loader: https://github.com/SplicePHP/css-loader

Example usage:

    module: {
        loaders: [ //default less loader
            {
                test: /\.less$/,
                loader: ["style-loader", "css-loader","less-loader"],
                exclude: [ //exclude package in order not to get loaded via this loader
                    /\package\/less\/style.less$/
                ]
            },
            { //created custom loader to fix wrong rel path issue
                test: /\package\/less\/style.less$/,
                loader: [
                    "style-loader",
                    "css-loader?rel="+path.resolve(__dirname,'path/to/package/dist/css'),
                    "less-loader"
                ]
            }
        ]
    }

I am not 100% sure that the regex is best practice for detecting relative urls, just pulled it from stack overflow. Works as far as I have tested. I tried to submit a pull request but I don't think it got through. If I don't hear anything by tomorrow I will resubmit the pull request to the css-loader git repo.

Not sure if I will be maintaining this fork, depends if another solution is found.

jhnns commented 8 years ago

Sorry guys, this feature is working under "normal" (=expected) circumstances. Please give us a minimal example so we can investigate this.

SplicePHP commented 8 years ago

Hi, here is an example repo that results in build errors: https://github.com/almasaeed2010/AdminLTE.git

npm:

npm install admin-lte --save-dev

entry.js

require('admin-lte/build/less/AdminLTE.less');

Webpack Config [I trimmed a lot of code out of my webpack config but here is the relevant code to illustrate the problem: have not tested this but I think you should get the idea. ]

var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: path.resolve(__dirname, "entry.js"),
    context: path.resolve(__dirname),
    output:
    {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
        chunkFilename: "[name]_[chunkhash:20].js",
        sourceMapFilename: "[name].map"
    },
    module:
    {
        loaders: [
            {
                test: /\.js$/,
                loader: "babel-loader",
                query:
                {
                    presets: ['es2015', 'react', 'stage-0']
                }
            },
            {
                test: /\.css$/,
                loader: ["style-loader", "css-loader"]
            },
            {
                test: /\.less$/,
                loader: ["style-loader", "css-loader", "less-loader"],
                exclude: [
                    // /\less\/AdminLTE.less$/
                ]
            },
            // {
            //     test: /\less\/AdminLTE.less$/,
            //     loader: [
            //         "style-loader",
            //         "css-loader?rel="+path.resolve(__dirname,'node_modules/admin-lte/dist/css'),
            //         "less-loader"
            //     ]
            // },
            {
                /*
                 * embed images and fonts smaller than 5kb
                 * 'image-webpack-loader?optimizationLevel=7&..........'
                 */
                test: /\.(gif|png|jpg|jpeg|svg)($|\?)/,
                loaders: ['url?limit=5000&hash=sha512&digest=hex&size=16&name=resources/[name]-[hash].[ext]']
            },
            {
                test: /\.(woff|woff2|eot|ttf)($|\?)/,
                loaders: ['url?limit=5000&hash=sha512&digest=hex&size=16&name=resources/[name]-[hash].[ext]']
            }
        ]
    }
};

I commented out the code I currently use to fix the issue.

So this is what happens: You include the less file from the admin-lte/build/less/ dir. When it hits the css loader it thinks it is in admin-lte/build/less/ and can't find relative resources. All the resources lay inside the admin-lte/dist/ dir. The less file is supposed to built to admin-lte/dist/css/ Now any resources required like: url(../img/someimage.jpg) will try to load admin-lte/build/img/someimage.jpg instead of admin-lte/dist/img/someimage.jpg The solution I propose is to make allowance for the build output dir to be specified in some way in order for the relative paths to load correctly in these cases. I used the rel keyword in my example. You can see an example of my hacked css-loader in the link supplied in the previous post.

Thanks.

jhnns commented 8 years ago

Thanks for providing an example @SplicePHP

Your assumptions are correct, but this is the expected behavior. The less-loader generates a single css file from multiple less files. That's why the css-loader tries to resolve all urls relatively from the less entry file.

When using webpack, it's best to put your binary assets like images and fonts inside your app or src or build folder (whatever you call it). Then you just reference them in your less file relatively like you would with any other JS import/require and things will work as expected. So, you don't need any path variable in your less files, because if you reference them relatively, things will just work.

SplicePHP commented 8 years ago

The problem is that most people are not in control of those variables. I am working on a pretty large project with a ton of node_modules and other git libraries. The standard is for less and scss files to be stored in another path to where the actual build is rendered to. Making these types of changes on third party libraries will be a nightmare for version control. I would have to fork nearly every git repo I use to modify the paths and I am not even sure that version control can be maintained with npm packages if these types of changes are made locally. This is a serious impediment for maintainability as far as I can tell, Stack Overflow is full of these bugs and no-one seems to have any answers. It is much easier to add a rel path variable to the css loader and no hacking of third party libraries is required. Making this small change fixes the problem and no backward compatibility is sacrificed. I will continue to use my custom css loader for now and will let you know if I find any bugs with my current implementation.

neutraali commented 7 years ago

Definitely a problem - We have to link our styles into one big lump from various sources, including some @ node_modules. However, we're running into a problem where (understandably) all the node_modules styles are trying to link to files within their own directories, resulting in a ton of errors and a failed compilation. It boggles me how with gulp-less everything just works, whereas trying to go with a pure webpack 2.0 -route with less-loader all we get are a ton of cryptic error messages .

jhnns commented 7 years ago

Just recognized that @SplicePHP has received a lot of upvotes. I'm not sure if I understand the use-case correctly – and maybe there are different expectations here.

If you don't agree with the current behavior, you can simply overwrite that by passing the relativeUrls: false option to the less-loader. If you don't want the css-loader to resolve urls, you can disable that.

If you don't want all of that behavior, it is still legitimate to just compile Less without touching webpack at all. All that URL resolving behavior makes sense if you want to treat every file as a single module, but if you don't want that, don't use webpack for your styles. It's ok to use webpack for JS only and handle CSS with a different tool. Webpack and gulp/grunt/npm-scripts are not the same, they serve slightly different purposes.

sekoyo commented 6 years ago

Relative urls has no effect when using programtically, I tried less.render(raw, { javascriptEnabled: true, relativeUrls: true }) but it still imports from root of the module

sberney commented 6 years ago

The situation is: there is less in one directory with imports relative to that directory, and less in a second directory with imports relative to that directory. The less in the second directory imports the less in the first directory.

gulp-less handles this with no problem. When I use webpack with less-loader, this is no longer handled. I tried adding the relativeUrls: false option, as well as relativeUrls: true. Neither of those make this situation work.

I haven't got it to a state where I can test that it's totally working, but after adding { url: false } to css-loader with { relativeUrls: false } it does compile.

Jogai commented 5 years ago

@sberney's solution works for @lipis's flag-icon-css but then breaks for zavoloklom's material-design-iconic-font

Using url loader doesnt help either, so how should I solve this?

cap-Bernardito commented 4 years ago

Fixed in master.

PS: To using properly flag-icon-css package, you must redefine @flag-icon-css-path variable:

@import "~flag-icon-css/less/flag-icon.less";

@flag-icon-css-path: '~flag-icon-css/flags';