bazilio91 / ejs-compiled-loader

EJS loader for webpack (without frontend dependencies)
MIT License
126 stars 72 forks source link

A valid query string passed to parseQuery should begin with '?' #46

Open beeryukov opened 4 years ago

beeryukov commented 4 years ago

ejs-compiled-loader version is 3.0.0 "webpack" is "4.43.0", "webpack-cli" is "3.3.11" Get the following error when building:

Module build failed (from ./node_modules/ejs-compiled-loader/index.js): Error: A valid query string passed to parseQuery should begin with '?' at Object.parseQuery (/media/data/web/node_modules/ejs-compiled-loader/node_modules/loader-utils/lib/parseQuery.js:13:11) at Object.module.exports (/media/data/web/node_modules/ejs-compiled-loader/index.js:15:102)

bazilio91 commented 4 years ago

Can you provide your loader rule from webpack config?

SamBroner commented 4 years ago

I also have this issue, I can resolve it by setting var query = {};

My loader rule is below.

            {
                test: /\.ejs$/, 
                use: {
                  loader: 'ejs-compiled-loader',
                  options: {
                    htmlmin: true,
                    htmlminOptions: {
                      removeComments: true
                    }
                  }
                }
              }

Here are my dev dependencies

    "@types/node": "^13.13.4",
    "copy-webpack-plugin": "^5.1.1",
    "css-loader": "^3.5.3",
    "ejs-compiled-loader": "^3.0.0",
    "eslint": "^6.8.0",
    "file-loader": "^6.0.0",
    "html-webpack-plugin": "^4.2.0",
    "image-webpack-loader": "^6.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "style-loader": "^1.2.0",
    "ts-loader": "^7.0.1",
    "typescript": "^3.8.3",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
SamBroner commented 4 years ago

@beeryukov, I believe I resolved this for myself.

I was using the HtmlWebpackPlugin like this:

        new HtmlWebpackPlugin({
            template: "!!ejs-compiled-loader!./public/index.ejs",
            templateParameters: {
                posts: postTemplates,
                post: false
            }
        }),

However, you can also just set the loader as a rule in your loader, HtmlWebpackPlugin will pick up this rule. So you can change the Html Webpack Plugin to not use the !!ejs-compiled-loader! syntax

            {
                test: /\.ejs$/, 
                use: {
                  loader: 'ejs-compiled-loader',
                  options: {
                    htmlmin: true,
                    htmlminOptions: {
                      removeComments: true
                    }
                  }
                }
              }

&

        new HtmlWebpackPlugin({
            template: "./public/index.ejs",
            templateParameters: {
                posts: postTemplates,
                post: false
            }
        }),

Kind of confusing between the READMEs, but solved my issue.

ZacxDev commented 4 years ago

I also had this issue, I resolved it by changing my webpack rule from:

    rules: [
    {
      test: /\.ejs$/,
      use: 'ejs-compiled-loader'
    }]

to:

  rules: [
    {
      test: /\.ejs$/,
      use: {
        loader: 'ejs-compiled-loader',
        options: {
          htmlmin: true,
          htmlminOptions: {
            removeComments: true
          }
        }
      }
    }]

It seems ejs-compiled-loader does not like options being undefined.

songxingguo commented 4 years ago

I also had this issue, but It can't be solved by the above method, So someone can help me, thank you.

ZacxDev commented 4 years ago

I also had this issue, but It can't be solved by the above method, So someone can help me, thank you.

Can you share your ejs webpack config rule?

songxingguo commented 4 years ago

I also had this issue, but It can't be solved by the above method, So someone can help me, thank you.

Can you share your ejs webpack config rule? webpack.config.js


const path = require('path');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MinCssExtractPlugin = require("mini-css-extract-plugin");   // 将css代码提取为独立文件的插件
const MediaQueryPlugin = require('media-query-plugin');

module.exports = { mode: 'production', entry: [ './src/index.js' ], output: { path: path.resolve(dirname, './dist'), filename: 'bundle.js' }, module: { rules: [{ test: /.s[ac]ss$/i, use: [ { loader: MinCssExtractPlugin.loader, // 将处理后的CSS代码提取为独立的CSS文件,可以只在生产环境中配置,但我喜欢保持开发环境与生产环境尽量一致 options: { publicPath: '/assets/', // only enable hot in development hmr: true, // if hmr does not work, this is a forceful method. reloadAll: true, }, }, 'css-loader', // CSS加载器,使webpack可以识别css文件 MediaQueryPlugin.loader, 'postcss-loader', // 承载autoprefixer功能,为css添加前缀 'sass-loader', // Compiles Sass to CSS ], }, { test: /.css$/, use: [{ loader: 'file-loader', options: { name: '[name].[hash:8].[ext]', outputPath: 'assets/', publicPath: './assets/' } }, 'extract-loader', 'css-loader', 'postcss-loader'] }, { test: /.(jpg|png|gif|svg)$/, use: { loader: 'file-loader', options: { name: '[name].[hash:8].[ext]', outputPath: 'assets/', publicPath: './assets/' } } }, { test: /.ejs$/, use: { loader: 'ejs-compiled-loader', options: { htmlmin: true, htmlminOptions: { removeComments: true } } } } ] }, plugins: [new HtmlWebpackPlugin({ template: 'src/index.ejs', inject: true, minify: { collapseWhitespace: true } }), new webpack.HotModuleReplacementPlugin(), new MinCssExtractPlugin(), new MediaQueryPlugin({ include: [ 'style', ], queries: { 'print': 'print', 'not print': 'not-print' } }) ], devServer: { contentBase: path.join(dirname, 'dist'), compress: true, overlay: true, port: 4550, open: true, hot: true } };

package.json
```javascript
{
  "name": "resume",
  "version": "1.0.0",
  "description": "个人简历",
  "main": "index.js",
  "repository": "",
  "author": "",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --mode=development",
    "build": "webpack --mode=production"
  },
  "devDependencies": {
    "@babel/core": "^7.10.2",
    "@babel/preset-env": "^7.10.2",
    "autoprefixer": "^9.1.5",
    "babel-loader": "^8.1.0",
    "css-loader": "^1.0.0",
    "cssnano": "^4.1.0",
    "ejs-compiled-loader": "^3.0.0",
    "ejs-loader": "^0.5.0",
    "extract-loader": "^2.0.1",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "media-query-plugin": "^1.3.1",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.14.1",
    "postcss-import": "^12.0.1",
    "postcss-loader": "^3.0.0",
    "postcss-px2rem-exclude": "0.0.6",
    "postcss-url": "^8.0.0",
    "px2rem-loader": "^0.1.9",
    "raw-loader": "^0.5.1",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.2.1",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "fs": "0.0.1-security",
    "lib-flexible": "^0.3.2"
  }
}
songxingguo commented 4 years ago

I also had this issue, but It can't be solved by the above method, So someone can help me, thank you.

Can you share your ejs webpack config rule? webpack.config.js

const path = require('path');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MinCssExtractPlugin = require("mini-css-extract-plugin");   // 将css代码提取为独立文件的插件
const MediaQueryPlugin = require('media-query-plugin');

module.exports = {
  mode: 'production',
  entry: [
    './src/index.js'
  ],
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [{
      test: /\.s[ac]ss$/i,
      use: [
        {
          loader: MinCssExtractPlugin.loader,  // 将处理后的CSS代码提取为独立的CSS文件,可以只在生产环境中配置,但我喜欢保持开发环境与生产环境尽量一致
          options: {
            publicPath: '/assets/',
            // only enable hot in development
            hmr: true,
            // if hmr does not work, this is a forceful method.
            reloadAll: true,
          },
        },
        'css-loader',  // CSS加载器,使webpack可以识别css文件
        MediaQueryPlugin.loader,
        'postcss-loader', // 承载autoprefixer功能,为css添加前缀
        'sass-loader', // Compiles Sass to CSS
      ],
    },
      {
        test: /\.css$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: '[name].[hash:8].[ext]',
            outputPath: 'assets/',
            publicPath: './assets/'
          }
        },
          'extract-loader',
          'css-loader',
          'postcss-loader']
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[hash:8].[ext]',
            outputPath: 'assets/',
            publicPath: './assets/'
          }
        }
      },
      {
        test: /\.ejs$/,
        use: {
          loader: 'ejs-compiled-loader',
          options: {
            htmlmin: true,
            htmlminOptions: {
              removeComments: true
            }
          }
        }
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin({
    template: 'src/index.ejs',
    inject: true,
    minify: {
      collapseWhitespace: true
    }
  }),
    new webpack.HotModuleReplacementPlugin(),
    new MinCssExtractPlugin(),
    new MediaQueryPlugin({
      include: [
        'style',
      ],
      queries: {
        'print': 'print',
        'not print': 'not-print'
      }
    })
  ],
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    overlay: true,
    port: 4550,
    open: true,
    hot: true
  }
};

package.json

{
  "name": "resume",
  "version": "1.0.0",
  "description": "个人简历",
  "main": "index.js",
  "repository": "",
  "author": "",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --mode=development",
    "build": "webpack --mode=production"
  },
  "devDependencies": {
    "@babel/core": "^7.10.2",
    "@babel/preset-env": "^7.10.2",
    "autoprefixer": "^9.1.5",
    "babel-loader": "^8.1.0",
    "css-loader": "^1.0.0",
    "cssnano": "^4.1.0",
    "ejs-compiled-loader": "^3.0.0",
    "ejs-loader": "^0.5.0",
    "extract-loader": "^2.0.1",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "media-query-plugin": "^1.3.1",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.14.1",
    "postcss-import": "^12.0.1",
    "postcss-loader": "^3.0.0",
    "postcss-px2rem-exclude": "0.0.6",
    "postcss-url": "^8.0.0",
    "px2rem-loader": "^0.1.9",
    "raw-loader": "^0.5.1",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.2.1",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "fs": "0.0.1-security",
    "lib-flexible": "^0.3.2"
  }
}

I solved this problem in other ways, This link

index.ejs:

<h1><%- require('./header.ejs')({ title: 'page name' }) -%></h1>
header.ejs:

<title><%= title %></title>
TacticalCode commented 4 years ago

It's a bug, this.query is blindly passed to utils.parseQuery() which throws the error "A valid query string passed to parseQuery should begin with '?'" if the string representation of that object does not start with a '?'. See https://github.com/bazilio91/ejs-compiled-loader/blob/3.x/index.js#L15

Steps to reproduce: Import without options object or query: '!!ejs-compiled-loader!./path/to/template' yields this error. Import with an undefined query: '!!ejs-compiled-loader?!./path/to/template' yields this error. Import with an empty object: '!!ejs-compiled-loader?{}!./path/to/template' yields no errors.

Debugging: I used this code inserted before https://github.com/bazilio91/ejs-compiled-loader/blob/3.x/index.js#L15

        console.log("this.query is set");
        console.log("this.query is of type " + typeof this.query);
        if(typeof this.query === 'object') console.log("query is an 'object'");
        else console.log("query is not an 'object'");
      }
      else {
        console.log("no query");
      }

The output of that is for !!ejs-compiled-loader!./path/to/tpl as well as !!ejs-compiled-loader?!./path/to/tpl

this.query is set
this.query is of type string

and for !!ejs-compiled-loader?{}!./path/to/tpl:

this.query is set
this.query is of type string
?{}

As such, loader-utils.parseQuery will throw that error if neither options nor a query is supplied. See https://github.com/webpack/loader-utils/blob/c937e8c77231b42018be616b784a6b45eac86f8a/lib/parseQuery.js#L11 for the parseQuery() code.

Note that the webpack loaders API documentation omits that this.query is set to an empty string if the query is "undefined". If that didn't happen, at least !!loader?!./path/to/tpl would work correctly. However, there is a shorthand to checking for an options object or passing the query to loader-utils.parseQuery() documented right below: https://webpack.js.org/api/loaders/#thisgetoptionsschema

Using this getOptions() would replace both Lines 12 and 15 in index.js, I assume.

OffTopic: To be honest, webpack is already at version 5 (beta) and this loader does some v4 stuff incorrectly in a branch called 3.x... I'm ditching this loader and giving that whole "do everything through webpack" a thorough reconsideration. There's an awful lot of dependencies that could break at any given moment (like this one). It was fun to learn this new-fangled webdev stuff, but I don't think I need that for my personal projects.

Thanks for playing.

tubbo commented 4 years ago

I'm also seeing this happen, and it definitely wasn't happening before. Using that weird !!ejs-compiled-loader?{}! prefix syntax fixed it for me, but that's just awful. My loader is pretty basic, and I'm not using this alongside HtmlWebpackPlugin, just a Webpacker app with Rails.