首页 > 解决方案 > How to handle dynamic image paths with webpack

问题描述

I've managed to process the majority of my icons within a large application but there are still two use cases which don't get caught.

i.e something along the lines of

class Comp {
   this.settingsLinks = [
     {name: 'advanced settings', icon: '/assets/icons/ic-settings.svg'}
   ]
}

and then that gets used within a template like so

<div ng-repeat="setting in $ctrl.settingsLinks">
  <md-icon md-svg-src="{{setting.icon}}"></md-icon>
</div>

My webpack config is like so

module.exports = {
  module: {
    loaders: [
      {
        test: /\.html$/,
        loaders: 'html-loader',
        options: {
          attrs: ['md-icon:md-svg-src', 'img:ng-src'],
          root: path.resolve(__dirname, '../src'),
          name: '[name]-[hash].[ext]'
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader',
        enforce: 'pre'
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name() {
            return '[name]-[hash].[ext]';
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loaders: [
          'ng-annotate-loader',
          'babel-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: conf.path.src('index.html')
    }),
    new ExtractTextPlugin('index-[contenthash].css'),
  ],
  output: {
    path: path.join(process.cwd(), conf.paths.dist),
    filename: '[name]-[hash].js'
  },
  entry: {
    app: [`./${conf.path.src('app/app.module.js')}`],
    vendor: Object.keys(pkg.dependencies)
  },
};

I've been looking at the webpack.ContextReplacementPlugin as a potential way of handling the dynamic paths does anyone have any insight into what I'm trying to achieve? I want to be able to hash names for cache busting purposes but am struggling to work out how to handle dynamic paths in both js and templates.

标签: javascriptangularjswebpack

解决方案


https://webpack.js.org/guides/dependency-management/

webpack gives access to require.context which allows telling webpack what the dynamic path could/should be, it removed the uncertainty of what is being required and allows you to return the newly hashed icon name from its original name. It does not require them all having an import cost it merely creates a map between the old and new name if I have understood correctly.

for example, this is saying grab all the file names in the icons folder grab the ones starting with ic- as that's our naming scheme for icons, this creates an object at build time I believe of all the possible icons to be used.

const ICONS = require.context('../../../assets/icons', true, /ic-[a-zA-Z0-9.-]/);

whats returned is a function where you can pass the original icon name and get back the hashed version. You can also use ICONS.keys() to get an array of icons.

here is an example usage I've used to provide some icons.

const ICONS = require.context('../../../assets/icons', true, /ic-[a-zA-Z0-9.-]/);

export function getIconFromPath(path) {
  return ICONS(path);
}

function getIconsFromPaths(obj) {
  Object.keys(obj).forEach(key => Object.assign(obj, {[key]: ICONS(obj[key])}));
  return obj;
}

export default getIconsFromPaths({
  ARCHIVED: './ic-status-warning.svg',
  CANCELLED: './ic-status-cancelled.svg',
  CONFLICT: './ic-status-warning.svg',
  DRAFT: './ic-status-draft.svg',
  EARLIER: './ic-status-published.svg',
  ENDED: './ic-status-ended.svg',
  ERROR: './ic-status-published-failure.svg',
  FAILURE: './ic-status-failure.svg',
  INVALID: './ic-status-warning.svg',
  IN_PROGRESS: './ic-content-publish-in-progress.svg',
  LATEST: './ic-status-published-latest.svg',
  LOCKED: './ic-status-locked.svg',
  PUBLISHED: './ic-status-published.svg',
  PUBLISHING: './ic-content-publish-in-progress.svg',
  SCHEDULED: './ic-status-scheduled.svg',
  SCHEDULING: './ic-content-publish-in-progress.svg',
  UNLOCKED: './ic-status-unlocked.svg',
  UPDATED: './ic-webhook-request-success.svg',
  UNPUBLISHING: './ic-content-publish-in-progress.svg',
  UNSCHEDULING: './ic-content-publish-in-progress.svg',
  VALID: './ic-content-valid-tick.svg',
  WARNING: './ic-status-warning.svg'
});

Because webpack now knows what could be returned from here it can now hash the name and you can do all sorts of good stuff like optimizing at build time.

so the example class given in my question would be resolved by

import { getIconFromPath } from '../icons/;

class Comp {
   this.settingsLinks = [
     {
       name: 'advanced settings',
       icon: getIconFromPath('./ic-settings.svg')
     }
   ]
}

推荐阅读