handlebars-loader icon indicating copy to clipboard operation
handlebars-loader copied to clipboard

`require` images

Open SimenB opened this issue 10 years ago • 30 comments

Hey.

When running a template through this plugin, stuff like

<h1 id="headerYo">{{name}} is kewl!</h1>
<h2>What is even life</h2>

<img src="../images/cat.jpg"/>

Doesn't replace the src with a require, like html-loader does. Is that an option? Images aren't being included.

I see inlineRequires, is that something I can use? If that's the case, #14 would be nice if completed :smile:

SimenB avatar May 01 '15 17:05 SimenB

includeRequires was indeed what was needed.

{ test: /\.hbs$/, loader: 'handlebars', query: { inlineRequires: '\/images\/' } }

Could that option maybe get a more prominent spot in the docs? And I think in the case of images, it should be handled automatically, like html-loader does it

SimenB avatar May 01 '15 18:05 SimenB

@altano Just a quick note about this, it only works if the images are in double quotes ", for some reason it fails of in single quotes '...

SimenB avatar May 04 '15 14:05 SimenB

Hey @altano, there is something weird with this. The following template does not get replaced

<input class="{{klasse}}" type="image" src="../../images/edit_blyant.png"/>

While this does get correctly replaced

<input src="../../images/edit_blyant.png" class="{{klasse}}" type="image"/>

The difference is that the source is at the start of the element.

Tagging @diurnalist as well, as he implemented it :smile:

My config is this

{ test: /\.hbs$/, loader: 'handlebars', query: { helperDirs: helperDirs, inlineRequires: '/images/' } }

SimenB avatar May 04 '15 16:05 SimenB

Sooo it looks to me like you should be running the html-loader after the handlebars loader. I assume the html-loader supplies the right functionality here with inline requires, and does so by properly parsing the HTML?

If you want this behavior to be automatic, you could name your files .html.hbs and bind that to both the handlebars and html loaders in your webpack config.

altano avatar May 05 '15 06:05 altano

html-loader has the correct behavior, yes. Handlebars doesn't return HTML though, it returns a JS function (I think?). Passing that through html-loader breaks. I get the following error.

ERROR in ./src/common/templates/header.hbs
Module not found: Error: Cannot resolve directory './\"../images/cat.jpg\"' in /Users/simen/Development/webpack-fun/src/common/templates
 @ ./src/common/templates/header.hbs 1:667-706

With this conf { test: /\.hbs$/, loader: 'html!handlebars' }.

I should note that it does work for us now, it just seems like there's something wrong using inlineRequires. If I was able to use html-loader, that'd be preferable of course.

And I need for it to spit out a JS function at the end, so I can call it with data later, of course

SimenB avatar May 05 '15 06:05 SimenB

html-loader will not work because the handlebars loader creates a JS function that spits out a template. This is why the inlineRequires option was added, actually - since it is impossible to effectively chain the handlebars-loader, I had to duplicate some of the functionality present in the other loaders that handle image/static assets automatically.

@SimenB I'm guessing it's a legit bug with the loader, either with the regex detection or there is some edge case missing in how we override the Handlebars compiler. Will assign this to me to look in to :) - thanks for the report!

diurnalist avatar May 05 '15 07:05 diurnalist

@diurnalist Regarding single vs double quote, see https://github.com/webpack/html-loader/issues/18

SimenB avatar May 09 '15 11:05 SimenB

@SimenB, you were really close: you want to specify this instead: { test: /\.hbs$/, loader: 'handlebars!html' } since, as you pointed out, handlebars-loader returns a function. handlebars-loader can process the output of html-loader but not the other way around. And I would actually recommend making the test test: /\.hbs\.html$/ to differentiate from regular handlebars templates, so that you can still process those using just handlebars-loader if you ever need that in the future.

This should work as long as html-loader can process your handlebars template, which should work for most templates.

I added an example to this loader to demonstrate, and it seems to work fine: https://github.com/altano/handlebars-loader/tree/master/examples/html-loader-chaining

The output of this example is:

module.exports = "<h1>Alan</h1>\r\n<img src=\"" + require("../images/cat.jpg") + "\"/>";

Let me know if that doesn't work for you.

altano avatar May 13 '15 06:05 altano

Doesn't work. It compiles fine, but this is what shows in Chrome Dev Tools.

image

It also doesn't process the image, it's just converted into a require-statement. Notice that it doesn't complain about missing cat.jpg, or missing loader for jpg (in your example).

For reference, this is using inlineRequires image

SimenB avatar May 13 '15 06:05 SimenB

@SimenB @altano having the same issue, compounded by the fact that I'm also using jade-html-loader and can't use query with multiple loaders. I'd love to find a solution!

chiplay avatar May 20 '15 03:05 chiplay

@SimenB @altano found the basic issue - html-loader returns a stringify module, not raw html. There is no raw query option for that loader, and even if there was, it would be impossible to use a query with multiple loaders. So... we either need handlebars-loader to handle img:src replacement automatically (like html-loader) or we need a new loader, eg. html-raw-loader, that could take the place of html-loader

chiplay avatar May 21 '15 14:05 chiplay

@chiplay using inlineRequires works for me, it's just a bit buggy atm. Regarding queries, you can use them inline, see http://webpack.github.io/docs/using-loaders.html#query-parameters. It's loader-utils under the hood so you can mess around with it to get the query right, I had that problem with multiple helperDirs

SimenB avatar May 23 '15 17:05 SimenB

@diurnalist any news on this?

JSteunou avatar Aug 18 '15 09:08 JSteunou

Also I dont understand, the documentation says it's a regExp, but putting a regExp do not work, it will select all node attributes without filtering, whereas putting a simple string containing piece of path works. That's really not clear and I had to pull my hair several times.

JSteunou avatar Aug 18 '15 09:08 JSteunou

The code calls new RegExp on what you pass in, doesn't check if it's already a regex. You can create your own regexp, then just call .source on it

SimenB avatar Aug 18 '15 09:08 SimenB

@chiplay has the right of it; this loader (and the HTML loader) don't compose very nicely. The inlineRequires option was something that was added mostly to support requiring things that need to be passed to helpers/partials. Here's a very contrived example:

{{$helperThatTakesConfig input "config/somewhere/in/here"}}

You could then use inlineRequires in your loader like:

{
  test: /\.tmpl$/,
  loader: "handlebars?inlineRequires=^(config)/"
}

And it would make it so your compiled template actually requires that module and passes it to the helper. @SimenB is right; it just puts it in a new RegExp call, nothing too fancy.

Looking at this again, it does look buggy, and when I get a chance I'll put together a 2.0 of it. It works for my current use-cases, so I just haven't had a big reason to dive in to it, sorry.

diurnalist avatar Aug 18 '15 10:08 diurnalist

@altano any progress? Just as @SimenB said, your example donen't work after compile sucessfully, and I wonder what I should do within this situation now?

weekeight avatar Sep 14 '15 08:09 weekeight

Have you tried putting src at the start of the tag?

SimenB avatar Sep 14 '15 09:09 SimenB

@weekeight the loaders are incompatible, I just didn't understand that fully earlier. I took a peek a few months ago but forgot to report back to here. I believe my conclusion was that someone should either fix inlineRequires or create an html-loader that returns strings that can be used in other template loaders such as this one.

I'm not actively looking at this, but @diurnalist said he might get around to putting together an improved version that is less buggy. Anyone else is free to take a stab at it as well and submit a PR.

altano avatar Sep 16 '15 20:09 altano

extract-loader after html-loader worked fine for me. e.g.

loaders: [
  'handlebars-loader',
  'extract-loader',
  'html-loader'
]

kazuma1989 avatar Feb 13 '18 09:02 kazuma1989

this code worked fine inlineRequires: '\/assets/images\/' my hbs templates in app/src/pages/home.hbs. and imgs in app/src/assets/images/

anotherWill avatar Mar 30 '18 03:03 anotherWill

I am using handlebars-loader with html-webpack-plugin and the following code worked for me. I also had to use file-loader. I hope this helps anyone because this took me a while to figure out.

webpack.config.js

const webpack = require('webpack');
const path = require('path');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, argv) => { 
	return {
		module: {
			rules: [
				{
					test: /\.(png|svg|jpg|gif)$/,
					include: [
						path.resolve(__dirname, 'src/assets/img')
					],
					use: [
						'file-loader'
					]
				},
				{
					test: /\.hbs$/,
					loader: 'handlebars-loader',
					query: { inlineRequires: '/img/' }
				},
			]
		},
		devServer: {
			contentBase: "./dist",
			compress: true,
			port: "9000",
		},
		watchOptions: {
			aggregateTimeout: 300,
			poll: 1000,
			ignored: /node_modules/
		},
		plugins: [		
			new HtmlWebpackPlugin({
				filename: 'my-awesome-website.html',				
				template: 'src/assets/html/my-awesome-website.hbs'
			}),
		]
	}
};

my-awesome-website.hbs

<img src="../img/image.jpg">

package.json

"handlebars": "^4.0.11",
"handlebars-loader": "^1.7.0",
"file-loader": "^1.1.11",
"webpack": "^4.8.3",
"webpack-cli": "^2.0.13",
"webpack-dev-server": "^3.1.1"

Folder structure

/assets
|-- /html
|-- /img

EddyVinck avatar Jun 22 '18 17:06 EddyVinck

Heya, inlinerequires works fine for me except if the img src url comes from the data. I am using this helper to resolve data:

module.exports = (source = 'pages', options) =>
    options.fn(require(`../components/${source}.json`));

And using it like so:

{{#fetchData 'HeaderModule/HeaderData'}}
   {{> HeaderModule/HeaderModule}}
{{/fetchData}}

Works fine if i hardcode the img src. Any ideas on how to make that work?

PS: If the img src is hardcoded that src url is replaced by the webpack output url for that img but if the img src comes from the json file then handlebars loader just ignores it (probably parsed too late) and is not replaced

fANZYo avatar Jun 24 '18 14:06 fANZYo

Solved: #120 (might help to checkout this out)

fANZYo avatar Jun 24 '18 16:06 fANZYo

Please reopen this issue.

Due to recent file-loader changes, file loader now loads MJS Modules by default instead of their flattened counterpart (meaning what you actually want to use is now hidden in the module.default property, making it impossible for HBS to detect).

If you want to use handlebars with webpack and subsequently inline requires in your handlebar templates, you need to disable es modules for the given file-loader

Example:

test: /\.(ttf|eot|woff|woff2)$/,
use: {
  loader: 'file-loader',
  options: {
    name: 'fonts/[name].[ext]',
    esModule: false //very important for HBS
  },
}

Edit: @katevoss notified me that along with my solution, you also need to include the inlineRequires property in the handlebars loader with a regex that matches your assets

juliankrieger avatar May 15 '20 14:05 juliankrieger

@pcardune I created a pull request to notify users of this in the Readme.

juliankrieger avatar May 15 '20 14:05 juliankrieger

I was also having this issue when I tried to load images (in /public) using handlebars. I fixed it with a combination of @juliankrieger 's options and inlineRequires. Here is the magic incantation that worked for me:

webpack.config.js

{
    test: /\.hbs$/,
    loader: "handlebars-loader",
    query: { inlineRequires: '\/public\/' }
},
{
    test: /\.(png|jpg|gif)$/,
    use: {
        loader: "file-loader",
        options: {
            esModule: false,
        },
    },
},

katevoss avatar Oct 22 '20 16:10 katevoss

@katevoss @pcardune Since I've been contacted for questions about my solution to this problem recently, maybe we should include @katevoss es example in the readme?

juliankrieger avatar Oct 27 '20 14:10 juliankrieger

Adding "esModule: false" to the "file-loader" options did the trick for me. I spent quite some time trying to get handlebars to work so here is my setup for reference.

package.json

"devDependencies": {
  "@babel/core": "^7.13.15",
  "@babel/preset-env": "^7.13.15",
  "babel-loader": "^8.2.2",
  "clean-webpack-plugin": "^3.0.0",
  "css-loader": "^5.1.1",
  "file-loader": "^6.2.0",
  "glob-all": "^3.2.1",
  "handlebars-loader": "^1.7.1",
  "mini-css-extract-plugin": "^1.3.9",
  "postcss": "^8.2.8",
  "postcss-loader": "^5.1.0",
  "postcss-preset-env": "^6.7.0",
  "purgecss-webpack-plugin": "^4.0.3",
  "sass": "^1.32.8",
  "sass-loader": "^11.0.1",
  "style-loader": "^2.0.0",
  "webpack": "^5.24.2",
  "webpack-cli": "^4.5.0",
  "webpack-dev-server": "^3.11.2",
  "webpack-merge": "^5.7.3"
},

webpack.config.js

module.exports = {
...
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: { importLoaders: 2 },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [["postcss-preset-env"]],
              },
            },
          },
          // 1. Compiles Sass to CSS
          "sass-loader",
        ],
      },
      {
        test: /\.(svg|png|jpe?g|gif)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name].[ext]",
            outputPath: "imgs",
            esModule: false,
          },
        },
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
      },
      {
        test: /\.hbs$/,
        loader: "handlebars-loader",
        options: {
          knownHelpersOnly: false,
          inlineRequires: /\/img\//,
          helperDirs: [path.join(__dirname, "./src/app/templates/helpers")],
          // partialDirs: [path.join(PATHS.TEMPLATES, "partials")],
        },
      },
    ],
  },
...
};

Folder structure

/webpack.config.js
/src
|-- /app
|---- /templates
|------ /myTemplateFile.hbs
|-- /img

myTemplateFile.hbs

<img src="./../../img/logoImage.png"/>

alexandermayorga avatar Apr 09 '21 20:04 alexandermayorga

Here is an example for webpack 5.x with html-webpack-plugin, layouts and images:

https://codesandbox.io/s/html-webpack-plugin-hbs-kuhgc?file=/webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.hbs$/,
        loader: "handlebars-loader",
        options: {
          inlineRequires: "/images/"
        }
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Home",
      template: "src/pages/home.hbs",
      filename: "index.html"
    })
  ]
};

footer.hbs

<footer>
  The Footer
  <img src="../images/footer.png">
</footer>

jantimon avatar Oct 10 '21 08:10 jantimon