David David - 9 days ago 5
React JSX Question

fixing relative paths with css-loader and react-router

I can't figure out how to configure webpack to produce css with valid urls no matter the current application url. Here's my project structure:

/theme
/assets
/css
/fonts
/images
/js
/dist
bundle.js
index.html
/node_modules
/src
/test
package.json
webpack.config.js


The
/theme
directory is a bootstrap theme. The urls in the css are relative. For example:

/* /theme/assets/css/ace-fonts.css */

@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(../fonts/OpenSans-300.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans'), local('OpenSans'), url(../fonts/OpenSans-400.woff) format('woff');
}


When I visit my application at it's root url(http://localhost:8080/) or a url with a single segment(http://localhost:8080/about) everything works. Those relative urls get resolved to the base url. However, if i refresh the browser while on a 2+ segment url(http://localhost:8080/some/thing) the browser ends up requesting the following url:

http://localhost:8080/some/db812d8a70a4e88e888744c1c9a27e89.woff

React router ends up architecting the right markup for the url, yet the urls in the stylesheets are relative, thus breaking. I'm new to webpack and have no idea how to go about fixing this.

Here's my webpack config:

var path = require('path');
var webpack = require('webpack');

var prodPlugins = [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
];

var defaultPlugins = [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.$": "jquery",
"window.jQuery": "jquery"
}),
new webpack.HotModuleReplacementPlugin()
];

module.exports = {
  entry: [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
  output: {
path: 'dist',
filename: 'bundle.js'
},
devServer: {
contentBase: './dist',
hot: true
},
resolve: {
root: path.resolve('./')
},
  module: {
    loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'react-hot!babel'
},
{
test: /\.css$/,
loader: 'style!css!resolve-url'
},
{ test: /\.less$/, loader: "style!css!less"},
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
{ test: /\.(png|jpg)$/, loader: 'url?limit=8192' }
]
  },
resolveUrlLoader: {
absolute: true
},
plugins: process.env.NODE_ENV === 'production' ? prodPlugins.concat(defaultPlugins) : defaultPlugins,
};


Any help would be much appreciated.

Answer

I ended up using the extract-text-webpack-plugin to put my css file at the root path. That fixed my relative paths in my css files problem but then I noticed the paths of images referenced in my components weren't correct so per @lux's suggestion i set my publicPath to /. Here is my updated webpack.config.js

var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

var prodPlugins = [
  new webpack.optimize.DedupePlugin(),
  new webpack.optimize.OccurrenceOrderPlugin(),
  new webpack.optimize.UglifyJsPlugin()
];

var defaultPlugins = [
  new webpack.ProvidePlugin({
    $: "jquery", 
    jQuery: "jquery",
    "window.$": "jquery",
    "window.jQuery": "jquery"
  }),
  new webpack.HotModuleReplacementPlugin(),
  new ExtractTextPlugin('styles.css')
];

module.exports = {
  entry: [
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/only-dev-server',
    './src/index.js'
  ],
  output: { 
    path: 'dist',
    publicPath: '/',
    filename: 'bundle.js'
  },
  devServer: {
    contentBase: './dist',
    hot: true
  },
  resolve: {
    root: path.resolve('./')
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'react-hot!babel'
      },
      {
        test: /\.css$/, 
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
      },
      { test: /\.less$/, loader: "style!css!less"},
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
      { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
      { test: /\.(png|jpg)$/, loader: 'url?limit=8192' }
    ]
  },
  resolveUrlLoader: {
    absolute: true
  },
  plugins: process.env.NODE_ENV === 'production' ? prodPlugins.concat(defaultPlugins) : defaultPlugins,
};

Because i'm extracting the css to a styles.css file, I had to update my dist/index.html file.

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My App</title>
    <link rel="stylesheet" type="text/css" href="/styles.css">
  </head>
  <body class="no-skin">
    <div id="app"></div>
    <script src="/bundle.js"></script>
  </body>
</html>