Skip to content

v0.28.8 regression: Relative urls in @font-face throw exception TypeError: url.replace is not a function #659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DawnWright opened this issue Jan 8, 2018 · 13 comments · Fixed by #663

Comments

@DawnWright
Copy link

DawnWright commented Jan 8, 2018

This bug was introduced in v0.28.8, and does not occur in v0.28.7 - it was introduced last week by #627

Repro:
Use a relative url inside a css @font-face, e.g.

@font-face {
    font-family: 'Test Font';
    src: url('../MyFont.woff2');
}

RESULT: css-loader throws exception TypeError: url.replace is not a function:

Module build failed: TypeError: url.replace is not a function
    at escape (webpack-internal:///1:11:26)
    at eval (webpack-internal:///0:7:71)
    at Object.<anonymous> (/redactedpath/node_modules/css-loader/index.js??ref--1-1!/redactedpath/node_modules/sass-loader/lib/loader.js!/redactedpath/node_modules/stripcomment-loader/index.js!/redactedpath/app/css/styles.scss:71:1)
    at __webpack_require__ (/redactedpath/node_modules/css-loader/index.js??ref--1-1!/redactedpath/node_modules/sass-loader/lib/loader.js!/redactedpath/node_modules/stripcomment-loader/index.js!/redactedpath/app/css/styles.scss:21:30)
    at /redactedpath/node_modules/css-loader/index.js??ref--1-1!/redactedpath/node_modules/sass-loader/lib/loader.js!/redactedpath/node_modules/stripcomment-loader/index.js!/redactedpath/app/css/styles.scss:64:18
    at Object.<anonymous> (/redactedpath/node_modules/css-loader/index.js??ref--1-1!/redactedpath/node_modules/sass-loader/lib/loader.js!/redactedpath/node_modules/stripcomment-loader/index.js!/redactedpath/app/css/styles.scss:67:10)
    at Module._compile (module.js:624:30)
    at Object.exec (/redactedpath/node_modules/webpack/lib/NormalModule.js:129:12)
    at /redactedpath/node_modules/extract-text-webpack-plugin/dist/loader.js:131:26
    at compile (/redactedpath/node_modules/webpack/lib/Compiler.js:300:11)```

Note, the URL is indeed a valid path, and I've confirmed that I get the appropriate ModuleNotFoundError with invalid relative paths. This bug only occurs when the relative path points to a valid file location.

Also note, the bug does not occur with an absolute path to the same file. Unfortunately, using absolute path is not a feasible workaround since the css in question is an external file we have no control over.

My understanding is that a relative url tells the css-loader to ignore the url. resolve therefore returns {} as the URL, which is passed to the new escape function and thus throws an exception calling replace:

module.exports = function escape(url) {
    // If url is already wrapped in quotes, remove them
    if (/^['"].*['"]$/.test(url)) {
        url = url.slice(1, -1);
    }
    // Should url be wrapped?
    // See https://drafts.csswg.org/css-values-3/#urls
    if (/["'() \t\n]/.test(url)) {
        return '"' + url.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"'
    }

    return url
}

We have reverted to v0.28.7 temporarily to avoid this bug.

@kenashcraft
Copy link

I have a related error, and reverting to 0.28.7 also fixes the problem.

    ERROR in ./src/styles/styles.scss
    Module build failed: /usr/src/app/src/styles/styles.scss:7
    exports.push([module.id, "@import url(\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,700\");\n@import url(\"https://fonts.googleapis.com/css?family=PT+Mono\");\nhtml {\n  line-height: 1.15; }\n\nbody {\n  margin: 0; }\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em; }\n\nb,\nstrong {\n  font-weight: bold; }\n\np {\n  margin-top: 0; }\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline; }\n\nsub {\n  bottom: -0.25em; }\n\nsup {\n  top: -0.5em; }\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-size: 100%;\n  line-height: 1.15;\n  margin: 0; }\n\nbutton,\nselect {\n  text-transform: none; }\n\nbutton,\nhtml [type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button; }\n\nprogress {\n  vertical-align: baseline; }\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto; }\n\n[ty
    
    TypeError: escape is not a function
        at /usr/src/app/src/styles/styles.scss:7:17930
        at ContextifyScript.Script.runInContext (vm.js:35:29)
        at ContextifyScript.Script.runInNewContext (vm.js:41:15)
        at Object.extractLoader (/usr/src/app/node_modules/extract-loader/lib/extractLoader.js:88:12)
     @ ./src/components/App.js 53:14-46
     @ ./src/index.js

@kgram, perhaps you have some ideas how to fix this?

@franklin-ross
Copy link

Yup, seeing exactly this when upgrading v0.28.7 to v0.28.8. Uncaught TypeError: url.replace is not a function in font-awesome.css.

@Sergey-Vatarmin
Copy link

Sergey-Vatarmin commented Jan 10, 2018

ERROR in ./node_modules/file-loader/dist/cjs.js?{"outputPath":"css/"}!./n
ules/extract-loader/lib/extractLoader.js!./node_modules/css-loader!./node
s/bootstrap/dist/css/bootstrap.css
Module build failed: TypeError: escape is not a function

"bootstrap": "3.3.2" is used

@kgram
Copy link
Contributor

kgram commented Jan 11, 2018

@DawnWright, could you expand on this part:

My understanding is that a relative url tells the css-loader to ignore the url. resolve therefore returns {} as the URL, which is passed to the new escape function and thus throws an exception calling replace.

If that is the case, I made the escape-functionality in complete ignorance of it. What happens in 0.28.7? Wouldn't it just output src: url([object Object]);? Is there an easy way to reproduce it? Is this functionality documented anywhere?

@kenashcraft, @jeysty: I believe you are seeing a different issue, although I don't get how it could occur. It seems like something goes wrong when requiring the escape-function. Could you have somehow effected require.resolve in your build process?

Would one of you mind trying to insert this log statement and posting the result? It should replace line 91 in the file node_modules/css-loader/lib/loader.js.

urlEscapeHelper = "var escape = require(" + loaderUtils.stringifyRequest(this, require.resolve("./url/escape.js")) + ");\nconsole.log('escape-function', '" + require.resolve("./url/escape.js") + "', " + loaderUtils.stringifyRequest(this, require.resolve("./url/escape.js")) + ", typeof escape);\n";

For me it logs something like escape-function <project>/node_modules/css-loader/lib/url/escape.js ../../../../node_modules/css-loader/lib/url/escape.js function. I'm just wondering what could go wrong with that require.

@ngyikp
Copy link

ngyikp commented Jan 12, 2018

For me, this is caused by an incompatibility with extract-loader and they just released a new patch version: peerigon/extract-loader#27

@gewisser
Copy link

after uptade to 0.28.8

ERROR in ../node_modules/materialize-css/dist/css/materialize.css
Module build failed: TypeError: url.replace is not a function
    at escape (webpack-internal:///1:9:26)
    at eval (webpack-internal:///0:7:77979)
    at Object.<anonymous> (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\css-loader\index.js!C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\materialize-css\dist\css\materialize.css:71:1)
    at __webpack_require__ (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\css-loader\index.js!C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\materialize-css\dist\css\materialize.css:21:30)
    at C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\css-loader\index.js!C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\materialize-css\dist\css\materialize.css:64:18
    at Object.<anonymous> (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\css-loader\index.js!C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\materialize-css\dist\css\materialize.css:67:10)
    at Module._compile (module.js:570:32)
    at Object.exec (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\NormalModule.js:129:12)
    at C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\extract-text-webpack-plugin\dist\loader.js:131:26
    at compile (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compiler.js:300:11)
    at applyPluginsAsync.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compiler.js:510:14)
    at next (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:202:11)
    at Compiler.<anonymous> (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\extract-text-webpack-plugin\dist\loader.js:112:7)
    at next (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:204:14)
    at Compiler.<anonymous> (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\CachePlugin.js:78:5)
    at Compiler.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:206:13)
    at compilation.seal.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compiler.js:507:11)
    at Compilation.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:195:46)
    at self.applyPluginsAsync.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:680:19)
    at Compilation.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:195:46)
    at self.applyPluginsAsync.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:671:11)
    at Compilation.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:195:46)
    at self.applyPluginsAsync.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:666:10)
    at Compilation.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:195:46)
    at sealPart2 (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:662:9)
    at Compilation.applyPluginsAsyncSeries (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:195:46)
    at Compilation.seal (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:605:8)
    at applyPluginsParallel.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compiler.js:504:17)
    at C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\tapable\lib\Tapable.js:289:11
    at _addModuleChain (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:507:11)
    at processModuleDependencies.err (C:\ProgramUser\ApacheDir\lk.nes-sys.ru2017\node_modules\webpack\lib\Compilation.js:477:14)
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)
 @ ../node_modules/materialize-css/dist/css/materialize.css
 @ ./main.js

Rollback to version 0.28.7. Everything is working.

@kgram
Copy link
Contributor

kgram commented Jan 16, 2018

@DawnWright, @franklin-ross, @gewisser: could any of you expand on what exactly in your build process results in this error? If @DawnWright is correct that the url is an empty object, a check could easily be added. I'd just like to understand why you'd ever need this functionality and how exactly it works first. Which loaders produce objects for what kind of files? Is it a special build-step? I couldn't reproduce with the gepetto-repo mentioned above, but it seems the CI only fails when starting testing in phantomjs, so maybe I'm just missing info about how this loader is used with others in more complicated builds.

@kgram
Copy link
Contributor

kgram commented Jan 16, 2018

@kenashcraft, @jeysty: can you confirm that you are using extract-loader and does an upgrade to [email protected] fix your problem with [email protected] as @ngyikp suggests?

@gewisser
Copy link

gewisser commented Jan 16, 2018

package.json:

{
  "name": "test_main",
  "description": "A Vue.js project",
  "version": "1.0.0",
  "author": "gewisser <[email protected]>",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack --watch",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  },
  "dependencies": {
    "ace-code-editor": "^1.2.3",
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-preset-env": "^1.6.1",
    "brace": "^0.11.0",
    "compression-webpack-plugin": "^1.1.3",
    "copy-webpack-plugin": "^4.3.1",
    "cross-env": "^5.1.3",
    "css-loader": "0.28.7",
    "deep-rename-keys": "^0.2.1",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.6",
    "html-webpack-plugin": "^2.30.1",
    "jquery": "^3.2.1",
    "materialize-css": "^0.100.2",
    "moment": "^2.20.1",
    "node-sass": "^4.7.2",
    "null-loader": "^0.1.1",
    "portal-vue": "^1.2.2",
    "pug": "^2.0.0-rc.4",
    "pug-loader": "^2.3.0",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.19.1",
    "vue": "^2.5.13",
    "vue-loader": "^13.7.0",
    "vue-resource": "^1.3.5",
    "vue-router": "^3.0.1",
    "vue-template-compiler": "^2.5.13",
    "vuex": "^3.0.1",
    "webpack": "^3.10.0"
  },
  "need": {
    "materializecss-vuejs-component": "*"
  },
  "devDependencies": {
    "webpack-runtime-analyzer": "^1.5.0"
  }
}

webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
//const RuntimeAnalyzerPlugin = require('webpack-runtime-analyzer');


function resolve (dir) {
    return path.join(__dirname, '..', dir)
}

module.exports = {
    context: path.resolve(__dirname, './src'),
    entry: {
        build: './main.js',
        framegantt: './gantt/index.js'
    },
    output: {
        path: path.resolve(__dirname, './dist'),
        //publicPath: '/dist/',
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        css: ExtractTextPlugin.extract({
                            use: 'css-loader',
                            fallback: 'vue-style-loader' // <- это внутренняя часть vue-loader, поэтому нет необходимости его устанавливать через NPM
                        })
                    }
                    // other vue-loader options go here
                }
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                options: {
                    plugins: ["syntax-dynamic-import"]
                }


            },
            {
                test: /\.pug$/,
                loader: "pug-loader",
                options: {
                    pretty: true
                }
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'file-loader',
                options: {
                    name: '[name].[ext]?[hash]'
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'null-loader'
            },
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: 'css-loader'
                })
            }
            ]
        },
        resolve: {
            extensions: ['.js', '.vue', '.json'],
            alias: {
                'vue$': 'vue/dist/vue.esm.js',
                '@': path.resolve(__dirname, 'src/'),
                'jquery': path.resolve(__dirname, 'node_modules/jquery/dist/jquery.js'),
                'utils': path.resolve(__dirname, 'src/utils.js')
            }
        },
        devServer: {
            historyApiFallback: true,
            noInfo: true
        },
        performance: {
            hints: false
        },

        devtool: '#eval-source-map',

        plugins: [
            // new RuntimeAnalyzerPlugin({
            //     // Can be `standalone` or `publisher`.
            //     // In `standalone` mode analyzer will start rempl server in exclusive publisher mode.
            //     // In `publisher` mode you should start rempl on your own.
            //     mode: 'standalone',
            //     // Port that will be used in `standalone` mode to start rempl server.
            //     // When set to `0` a random port will be chosen.
            //     port: 0,
            //     // Automatically open analyzer in the default browser. Works for `standalone` mode only.
            //     open: true,
            //     // Use analyzer only when Webpack run in a watch mode. Set it to `false` to use plugin
            //     // in any Webpack mode. Take into account that a building process will not be terminated
            //     // when done since the plugin holds a connection to the rempl server. The only way
            //     // to terminate building process is using `ctrl+c` like in a watch mode.
            //     watchModeOnly: false
            // }),
            new ExtractTextPlugin("[name].style.css"),
            new HtmlWebpackPlugin({
                filename: 'index.html',
                chunks: ['index'],
                template: 'index.pug'
            }),
            new webpack.ProvidePlugin({
                $: 'jquery',
                jQuery: 'jquery',
                'window.$': 'jquery',
                'window.jQuery': 'jquery',
                'utils': 'utils'
            }),
            new CopyWebpackPlugin([
                    {from: 'include', to: 'include'},
                    {from: 'config.php'},
                    {from: 'db_config.php'},
                    {from: 'index.php'},
                    {from: '.htaccess'},
                    {from: 'gantt/iframeGantt.html'},
                    {from: 'aceeditor/ace', to: 'ace'},
                    {from: 'aceeditor/aceeditor.html'},
                    {from: 'vendor', to: 'vendor'}
                ], {
                copyUnmodified: true
            }),
            new webpack.optimize.CommonsChunkPlugin({
                name: 'common' // Specify the common bundle's name.
            }),
            new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /ru/)
        ]

};

if (process.env.NODE_ENV === 'production') {
    module.exports.devtool = '#source-map';
    //module.exports.output.path = path.resolve(__dirname, './prod');
    module.exports.module.rules[2].options.pretty = false;
    // http://vue-loader.vuejs.org/en/workflow/production.html
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: '"production"'
            }
        }),
        new webpack.optimize.UglifyJsPlugin({
            sourceMap: true,
            compress: {
                warnings: false
            }
        }),
        new webpack.LoaderOptionsPlugin({
            minimize: true
        })/*,
        new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.(css|js)$/,
            threshold: 10240,
            minRatio: 0.8
        })*/
    ]);
}

materialize.css:
https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css

@kgram
Copy link
Contributor

kgram commented Jan 16, 2018

@gewisser: Perfect, everything's becoming clear after playing around with that for a bit. The null-loader transforms everything into an empty file. Webpack defaults module.exports to an empty object, which is where that comes from. So really you just want to ignore the urls, the object is incidental.

You are effectively getting that with 0.28.7, although you should be aware that this is only because the url is transformed into src: url([object Object]);, which is an invalid property in CSS (no spaces allowed in unquoted urls). If 0.28.8 had handled objects, you would probably start seeing requests to /[object+Object].

It's possible the invalid property is the best solution, since there is no good way to avoid a request without removing url(...) too.

I'll look into making a pull-request.

@franklin-ross
Copy link

franklin-ross commented Jan 17, 2018

I'm no longer seeing this, but I don't remember the exact set of package updates that got me in trouble in the first place. I've tried just updating all my Webpack loaders and whatnot to their latest and it seems fine.

There's no extract-loader in my project at all though, so that's unrelated for me.

@michael-ciniawsky
Copy link
Member

Released in v0.28.9 🎉 Please try it out and report back if still regressions :)

@gewisser
Copy link

v0.28.9 now works with null-loader. There are no errors.

@font-face {
  font-family: "Roboto";
  src: local(Roboto Regular), url([object Object]) format("woff2"), url([object Object]) format("woff");
  font-weight: 400;
}

Thanks to this problem, I now know where it comes from: url([object Object])
:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants