42

I'm trying to set up an angular project using Webpack but I can't figure out how to reference images from within html templates and have them included in the build.

My project tree is as follows:

package.json
app/
- images/
  - foo.png
- scripts/
- styles/
- templates/

I'm trying to use html-loader along with url-loader and file-loader but it's just not happening.

This is an example template: app/templates/foo.html

<img src="../images/foo.png" />

Problem #1: I would like to be able to reference images relative to app/. Right now, the paths need to be relative to the template file and this will get ugly very quickly (../../../images/foo.png).

Problem #2: Even if I specify the relative path, as I have done above, the project builds successfully but nothing really happens. The paths are left as-is and no images appear in dist/.

Here is my webpack config:

var path = require('path');
var webpack = require('webpack');
var ngminPlugin = require('ngmin-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
module.exports = function(config, env) {
  var appRoot = path.join(__dirname, 'app/')
  if(!env) env = 'development';
  var webpackConfig = {
    cache: true,
    debug: true,
    contentBase: appRoot,
    entry: {
      app: path.join(appRoot, '/scripts/app.coffee')
    },

    output: {
      path: path.join(__dirname, 'dist/),
      publicPath: '/',
      libraryTarget: 'var',
      filename: 'scripts/[name].[hash].js',
      chunkFilename: '[name].[chunkhash].js'
    },

    module: {
      loaders: [
        {
          test: /\.css$/,
          loader: ExtractTextPlugin.extract("style-loader", "css-loader")
        },
        {
            test: /\.scss$/,
            loader: ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader?outputStyle=expanded&includePaths[]=./node_modules/foundation/scss/')
        },
        {
          test: /\.coffee$/,
          loader: 'coffee-loader'
        },
        {
          loader: 'ngtemplate?relativeTo=' + (path.resolve(__dirname, './app')) + '/!html'
        },
        {
          test: /\.png$/, loader: "url-loader?limit=100000&mimetype=image/png&name=[path][name].[hash].[ext]"
        },
        {
          test: /\.jpg$/, loader: "file-loader?name=[path][name].[hash].[ext]"
        },
        {
          test: /\.(woff|woff2)(\?(.*))?$/,
          loader: 'url?prefix=factorynts/&limit=5000&mimetype=application/font-woff'
        },
        {
          test: /\.ttf(\?(.*))?$/,
          loader: 'file?prefix=fonts/'
        },
        {
          test: /\.eot(\?(.*))?$/,
          loader: 'file?prefix=fonts/'
        },
        {
          test: /\.svg(\?(.*))?$/,
          loader: 'file?prefix=fonts/'
        },
        {
          test: /\.json$/,
          loader: 'json'
        }
      ]
    },

    resolve: {
      extensions: [
        '',
        '.js',
        '.coffee',
        '.scss',
        '.css'
      ],
      root: [appRoot],
    },

    singleRun: true,
    plugins: [
      new webpack.ContextReplacementPlugin(/.*$/, /a^/),
      new webpack.ProvidePlugin({
        '_': 'lodash'
      }),
      new ExtractTextPlugin("styles/[name].[chunkhash].css", {allChunks: true}),
      new HtmlWebpackPlugin({
        template: appRoot + '/app.html',
        filename: 'app.html',
        inject: 'body',
        chunks: ['app']
      })
    ],
    devtool: 'eval'
  }

  if(env === 'production') {
    webpackConfig.plugins = webpackConfig.plugins.concat(
      new ngAnnotatePlugin(),
      new webpack.optimize.UglifyJsPlugin(),
      new webpack.DefinePlugin({
        'process-env': {
          'NODE_ENV': JSON.stringify('production')
        }
      }),
      new webpack.optimize.DedupePlugin(),
      new webpack.optimize.UglifyJsPlugin()
    );
    webpackConfig.devtool = false;
    webpackConfig.debug = false;
  }
  return webpackConfig;

}

Quiver
  • 1,351
  • 6
  • 33
  • 56
syko
  • 515
  • 1
  • 5
  • 9

5 Answers5

37

If you are using HTML templates in Webpack 2, in addition to use the file-loader you need to change in your HTML:

<img src="../images/foo.png" />

to this

<img src=<%=require("../images/foo.png")%> />

Dev01
  • 13,292
  • 19
  • 70
  • 124
Luis Múzquiz
  • 471
  • 4
  • 4
22
  1. Yes, you will have to do so for loading images from different path.
  2. I had similar issue and I resolved this using file loader:

.

loaders: [{
  // JS LOADER
  test: /\.js$/,
  loader: 'babel-loader?optional[]=runtime',
  exclude: /node_modules/
}, {
  // ASSET LOADER
  test: /\.(woff|woff2|ttf|eot)$/,
  loader: 'file-loader'
},
{
  //IMAGE LOADER
  test: /\.(jpe?g|png|gif|svg)$/i,
  loader:'file-loader'
},
{
  // HTML LOADER
  test: /\.html$/,
  loader: 'html-loader'
},
{
  //SCSS LOADER
  test: /\.scss$/,
  loaders: ["style-loader", "css-loader", "sass-loader?indentedSyntax"]
}
]

Good Luck

Spencer
  • 2,245
  • 3
  • 28
  • 50
A_J
  • 1,635
  • 1
  • 18
  • 31
  • 6
    It's no longer allowed to omit the '-loader' suffix when using loaders. You need to specify 'file-loader' instead of 'file'. – Stone Mar 14 '17 at 01:36
  • Not sure how this solves @syko's as it looks like the same setup he has. Are there any `file-loader` options that will allow to simple have html files reference a common reference point (even @syko app folder is a start), so one could do ``, since the directory structure of images is less likely to change than many template files. Luis's answer below is most correct, since module aliasing can also be used, but adding in the require looks ugly and not very intuitive. – Guy Park Aug 20 '18 at 04:17
  • 1
    @GuyPark if you want the equivalent to `` with file-loader, you can import the image from your .js file and add it to your template via raw javascript code. – klewis Mar 21 '19 at 15:07
  • A nice touch, but unfortunately this is a dynamic image loaded from an [authenticated] API service, so it's a bit more tricky, otherwise I would. :) – Guy Park Mar 25 '19 at 07:52
11

For Webpack 5 you have to write this way:

module: {
  rules: [
    // other stuff above.....
    {
      test: /\.html$/,
      use: [
        {
          loader: 'html-loader'
        }
      ]
    }
  ]
}
OKi
  • 169
  • 1
  • 6
5

With Webpack 4, I was able to solve this problem by updating by html-loader to specify the root for my files. So given @syko's original structure; in webpack.config.js...

module: {
  rules: [
    // other stuff above.....
    {
      test: /\.html$/,
      use: [
        // ...The other file-loader and extract-loader go here.
        {
          loader: 'html-loader'
          options: {
            // THIS will resolve relative URLs to reference from the app/ directory
            root: path.resolve(__dirname, 'app')
          }
        }
      ]
    }
  ]
}

This tells the html-loader to interpret all absolute urls from the /app folder. So in our app/templates/foo.html, you can then use the following...

<img src="/images/foo.png" />

This then tells html-loader to see the above as path.resolve(__dirname, 'app', 'images', 'foo.png'). Then if you have extract-loader and/or file-loader, all the paths should be correct.

Managed to get this solution after hammering away at it for a while. The biggest confusion is where about in the loader stack the /images/foo.png is interpreted, in this case it starts at the html-loader plugin. Everything else after that, is more about how the image files are to be published.

Hope that helps.

Guy Park
  • 959
  • 12
  • 25
4

You can use file-loader to extract images. Then using html-loader you can specify which tag-attribute combination should be processed by this loader via the query parameter attrs.

I could make it work with this configuration:

{
    test: /\.(jpe?g|png|gif|svg|ico)$/i,
    use: [{
        loader: 'file-loader',
        options: {
            name: '[name].[ext]',
            outputPath: 'images/'
        }
    }]
},
{
    test: /\.(html)$/,
    use: {
        loader: 'html-loader',
        options: {
            attrs: ['img:src', 'link:href']
        }
    }
}

Or in angular something like this:

attrs: [':ng-src', ':ng-href']
osmanz
  • 481
  • 5
  • 15