Images GETs throw 404 error in production using Webpack and AngularJS
Asked Answered
G

4

7

I have this conundrum I can't solve after many tryouts:

I'm unable to load static images in production;

everything is fine while developing (npm run serve), even when serving files from dist (npm run serve:dist)

Versions

Webpack: 3.12.0

file-loader: 1.1.11

url-loader: 1.1.2

AngularJS: 1.6.9

While developing:

My index.html contains this line and webpack won't load without

<base href="/"></base>

while my AngularJS config block contains

$locationProvider.hashPrefix('');

I use an URL like http://localhost:3000/#/appName/listingComponent/users

and I'm able to see every image using the ng-src (sometimes it's dynamically binded) like this

<img ng-src="app/images/image1.png">

the folder structure is like this

ui/
├── conf/
│   ├── browsersync-dist.conf.js
│   ├── browsersync.conf.js
│   ├── gulp.conf.js
│   ├── webpack-dist.conf.js
│   └── webpack.conf.js
├── gulp_tasks/
│   ├── browsersync.js
│   ├── misc.js
│   └── webpack.js
├── node_modules/
├── src/
│   ├── app/
│   │   ├── config.js  //global variables
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── index.js
│   │   ├── index.less
│   │   ├── images/ <--
│   │   ├── actual_app_folders/
│   │   └── ...
├── gulpfile.js
└── package.json

In my webpack-dist.js I have the following

module.exports = {
  module: {
    loaders: [
      {
        test: /\.json$/,
        loaders: [
          'json-loader'
        ]
      },
      {
        test: /\.js$/,
        exclude: [
          /node_modules/
        ],
        loader: 'babel-loader',
        options: {
          presets: ['es2015']
        }
      },
      {
        test: /\.js$/,
        exclude: [
          /node_modules/
        ],
        loaders: [
          'eslint-loader'
        ],
        enforce: 'pre'
      },
      {
        test: /\.(css|less)$/,
        exclude: '/node_modules/roboto-fontface/',
        loaders: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader?minimize!less-loader!postcss-loader'
        })
      },
      {
        test: /\.html$/,
        loaders: [
          'html-loader'
        ]
      },
      {
        test: /\.(jpe?g|png|gif|svg|eot|woff|ttf|svg|woff2)$/,
        loader: 'url-loader?name=[name].[ext]'
      }
    ]
  },
  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    FailPlugin,
    new HtmlWebpackPlugin({
      template: conf.path.src('/app/index.html'),
      inject: true,
      chunksSortMode: 'dependency'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: ['vendors', 'config'],
      minChunks: Infinity
    }),
    new ExtractTextPlugin('index-[contenthash].css'),
    new webpack.LoaderOptionsPlugin({
      options: {
        postcss: () => [autoprefixer]
      }
    })
  ],
  output: {
    path: conf.paths.dist,
    filename: function(output) {
      return output['chunk']['name'] === 'config' ? '[name].js' : '[name]-[hash].js';
    },
    chunkFilename: '[name]-[hash].js'
  },
  entry: {
    app: `./${conf.path.src('/app/index')}`,
    config: `./${conf.path.src('/app/config')}`,
    vendors: Object.keys(pkg.dependencies).concat(['webpack-material-design-icons'])
  }
};

IN PRODUCTION

The build process adds the /customer_company_name/our_company_name/ prefix to the path, which becomes

http://<customer_domain>:<domain_port>/customer_company_name/our_company_name/#/appName/listingComponent/users

while the folder structure is like this

our_company_name/
├── app/
│   ├── images/ <--
│   │   ├── image1.png
│   │   └── image2.png
├── app-<random_number>.js
├── config.js
├── favicon.ico
├── index.html
├── index-<random_number>.css
└── vendors.js

THE PROBLEM

Now every time there's a pic to be shown, the browser gets a 404 error like this

GET http://<customer_domain>:<domain_port>/app/images/image1.png 404 (Not Found)

while the request should be made like this

GET http://<customer_domain>:<domain_port>/customer_company_name/our_company_name/index.js

like every other file in the application does.

What I tried

So far nothing of the following helped:

  1. Change base tag upon build process in the following <base href="/customer_company_name/our_company_name/"></base> (this is currently retained)
  2. Explicitly requiring the pictures together with every other file in the index.js require('./images/ODM_trasp48px.png'); (this is currently retained)
  3. Using publicPath in webpack output.publicPath: '/customer_company_name/our_company_name/' and also output.publicPath: '/'
  4. Using file-loader instead of url-loader (this is currently retained)
  5. Using file-loader with options: { useRelativePath: true }
  6. Using file-loader with options: { publicPath: /customer_company_name/our_company_name/ }

Can you help me lads, please?

Gottlieb answered 11/1, 2019 at 15:32 Comment(5)
Wild guess, but have you tried $locationProvider.html5Mode(true); to eliminate the hashbang? Also, the base tag must end with a slash and the sources must not start with a slash. As far as I can tell, this should have worked out of the box. Could you provide an angular template with img tags?Shiner
In the beginning, I had $locationProvider.html5Mode(true) but it made the app throw 404 errors when trying to redirect to a specific page once the app crashed (via $location.path('/'); return $window.location.reload();). I agree that it should have worked out of the box, even using the hashbang; the other thing I initially did was moving the index.* files from src/ to src/app. The base tag has already a trailing slash in its src, are you referring to something else?Gottlieb
1. $location.path('/') doesn't work without reloading the page? 2. Have you tried moving the static files out of the src folder, and tell webpack to copy them to the desired location?Shiner
1. yes, but that's the use we meant for the "redirect", probably a misuse of the term indeed 2. no since that looks I'll have the same problem, but with extra steps P.S.: I think in the previous comment you meant "the base tag is a self-closing type" thank you for the observationGottlieb
are you deploying it on iis?Podium
G
0

SOLUTION Let's start with

output.publicPath: '/customer_company_name/our_company_name/'

this will take care of rewriting ng-/src paths correctly as explained here.

We won't need the html-loader to parse ng-/src, the only thing needed is to always use ng-src, or the build won't be successful.

Eventually, there's no need of <base> tag and neither the initial slash in every path used to refer to images.

I'll update this answer as I improve the webpack-dist.conf.js/the whole project for the better.


No answer given worked per se, hence I'll try combinations of them since some suggestions look legit.

Eventually, if I won't be able to solve this, I'll try to drop the production path prefix /customer_company_name/our_company_name/.

Gottlieb answered 21/1, 2019 at 9:5 Comment(0)
F
0

I think the problem is not with the replacement pattern since it works for other file types, but the fact that webpack does not know what to do with ng-src attribute. I think it only looks at the img's src attribute by default. You can change it to recognize ng-src like:

module.exports = {
  module: {
    loaders: [
    ...
    {
      test: /\.html$/,
      loaders: [ "html-loader?" + JSON.stringify({ attrs: ["img:src", "img:ng-src"] })]
    },
    ...
  ],
   ...

alt syntax:

{
   test: /\.html$/,
   loaders: [ "html-loader?attrs=img:src img:ng-src" ]
}

Before changing your config, you can just test whether that is the reason by checking if the replacement currently works correctly for img tags with a plain static <img src="..."/>.

Fi answered 18/1, 2019 at 3:58 Comment(3)
This looks legit, but I'm currently trying to add it and it doesn't seem to work. Since there's a lot more in play in that webpack-dist.js can you please add it to the original file together with the other parts I should retain/add to it?Gottlieb
Just use it to replace the section for "html-loader" in your loaders. If it makes no difference, then the reason is something else.Fi
Well, it doesn't work per se, but I'll try it in different combinations of file-loader options and src pathsGottlieb
Y
0

Instead of letting webpack parsing your templates, you might try to explicitly import your image(s) and pass it to your template.

The imported images will be then processed by the configured image-loader or url-loader.

Static image

const myImage = require('./path/to/images/image1.png');

// In component declaration 
`<img ng-src="${myImage}">`

Dynamic image

Using webpack's require.context

const pathToImages = require.context('./path/to/images', true);
const dynamicImageName = 'foo.jpg',

// In component declaration 
`<img ng-src="${pathToImages(dynamicImageName, true)}">`
Yakutsk answered 20/1, 2019 at 10:53 Comment(0)
C
0

Can you try below change into your index.html

<base href="./"></base>
Cence answered 21/1, 2019 at 6:45 Comment(0)
G
0

SOLUTION Let's start with

output.publicPath: '/customer_company_name/our_company_name/'

this will take care of rewriting ng-/src paths correctly as explained here.

We won't need the html-loader to parse ng-/src, the only thing needed is to always use ng-src, or the build won't be successful.

Eventually, there's no need of <base> tag and neither the initial slash in every path used to refer to images.

I'll update this answer as I improve the webpack-dist.conf.js/the whole project for the better.


No answer given worked per se, hence I'll try combinations of them since some suggestions look legit.

Eventually, if I won't be able to solve this, I'll try to drop the production path prefix /customer_company_name/our_company_name/.

Gottlieb answered 21/1, 2019 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.