fixing relative paths with css-loader and react-router - reactjs

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.

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>

Related

How to import SASS file in React Webpack relative to project's root directory

I want to be able to import a SASS file in a React component relative to the project's root directory, as opposed to having to do it relative to the component.
I want to be able to do the following in the componenet:
import styles from "styles/popup.sass"
as opposed to
import styles from "../../styles/popup.sass"
I used the resolve options in order to be able to do this and it works for other file types (i.e. .js and .png), but it does not work for .sass files. I get the following error:
Cannot find module 'styles/popup.sass'
I'm not sure why this isn't working for SASS files and would really appreciate any help.
Project Structure:
src
- js
- popup
- greeting_componenet.jsx
- styles
- popus.sass
Webpack Config file
var webpack = require("webpack"),
path = require("path"),
fileSystem = require("fs"),
env = require("./utils/env"),
CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin,
CopyWebpackPlugin = require("copy-webpack-plugin"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
WriteFilePlugin = require("write-file-webpack-plugin");
// load the secrets
var alias = {};
var secretsPath = path.join(__dirname, ("secrets." + env.NODE_ENV + ".js"));
var fileExtensions = ["jpg", "jpeg", "png", "gif", "eot", "otf", "svg", "ttf", "woff", "woff2"];
if (fileSystem.existsSync(secretsPath)) {
alias["secrets"] = secretsPath;
}
var options = {
mode: process.env.NODE_ENV || "development",
entry: {
popup: path.join(__dirname, "src", "js", "popup.js"),
options: path.join(__dirname, "src", "js", "options.js"),
background: path.join(__dirname, "src", "js", "background.js")
},
output: {
path: path.join(__dirname, "build"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader",
include: [
path.join(__dirname, 'src'),
/node_modules\/(semantic-ui-css)/
],
},
{
test: /\.(scss|sass)$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: "[path]___[name]__[local]___[hash:base64:5]",
},
}
},
{
loader: 'postcss-loader',
options: {
plugins: function() {
return [require('autoprefixer')]
}
}
},
'sass-loader'
],
exclude: /node_modules/
},
{
test: new RegExp('\.(' + fileExtensions.join('|') + ')$'),
loader: "file-loader?name=[name].[ext]",
include: [
path.join(__dirname, 'src'),
/node_modules\/(semantic-ui-css)/
],
},
{
test: /\.html$/,
loader: "html-loader",
exclude: /node_modules/
},
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/
}
]
},
resolve: {
alias: alias,
extensions: fileExtensions.map(extension => ("." + extension)).concat([".jsx", ".js", ".css", ".sass"]),
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
},
plugins: [
// clean the build folder
new CleanWebpackPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new CopyWebpackPlugin([{
from: "src/manifest.json",
transform: function (content, path) {
// generates the manifest file using the package.json informations
return Buffer.from(JSON.stringify({
description: process.env.npm_package_description,
version: process.env.npm_package_version,
...JSON.parse(content.toString())
}))
}
}]),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "popup.html"),
filename: "popup.html",
chunks: ["popup"]
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "options.html"),
filename: "options.html",
chunks: ["options"]
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "background.html"),
filename: "background.html",
chunks: ["background"]
}),
new WriteFilePlugin()
]
};
if (env.NODE_ENV === "development") {
options.devtool = "cheap-module-eval-source-map";
}
module.exports = options;
You need to add aliases for styles.
Something like:
module.exports = {
//...
resolve: {
alias: {
styles: path.resolve(__dirname, 'src/styles/'),
js: path.resolve(__dirname, 'src/js/')
}
}
};

toastr not showing when imported in a jsx file

I have a react app that uses webpack to bundle JS and CSS into 1 file and output it into a destination folder. I've recently added toastr to 1 of my jsx file:
import toastr from "toastr";
import "toastr/build/toastr.min.css"
Running the app and viewing the source, i've verified in the browser (viewing the source files) that toastr.min.js is included in the JS bundle and toastr.min.css is included in the CSS bundle. However, the toastr notification doesn't show. There is no error and a scrollbar appears in the right-side for a few seconds so I suspected the toastr code is working, just that the CSS is not properly styling for some reason.
I removed this line:
import "toastr/build/toastr.min.css"
and then directly added this to html
<link rel="stylesheet" type="text/css" href="~/css/toastr.min.css" />
and now it works. But I want to make it work where toastr.min.css is included in the bundle. Is there anything I'm missing?
webpack config
const path = require("path");
const webpack = require("webpack");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
home: "./Scripts/Components/Home/main.js",
login: "./Scripts/Components/Login/main.js",
vendor: [
"jquery",
"react",
"react-dom",
"react-router-dom",
"react-css-modules",
]
},
mode: "development",
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "all",
name: "vendor",
test: "vendor",
enforce: true
}
}
}
},
output: {
publicPath: "/js/",
path: path.join(__dirname, "/wwwroot/js/"),
filename: "[name].bundle.js"
},
devtool: "source-map",
plugins: [
new miniCssExtractPlugin({
filename: "../css/[name].css"
}),
],
module: {
rules: [{
test: /\.jsx$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["env", "react"]
}
}
}, {
test: /\.css$/,
use: [{
loader: miniCssExtractPlugin.loader,
}, {
loader: "css-loader",
query: {
modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]"
}
}]
}]
}
};

after splitting Chunks in webpack unable to run app

in my react app i'm using webpack 4,before implementing split chunk everything is working fine but after splitChunks it generating vendor.js file and it showing on html file also but page is blank.
my webpack file
const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const CompressionPlugin = require('compression-webpack-plugin');
const VENDOR_LIBS =[
'antd','axios','moment','rc-time-picker','react',
'react-dom','react-ga','react-google-maps','react-loadable',
'react-redux','react-router','react-router-dom','recompose','redux','redux-thunk'
];
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry:{
vendor: VENDOR_LIBS,
main: './src/app.js',
},
output: {
path: path.join(__dirname, 'public'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.s?css$/,
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
},{
test: /\.(gif|svg|jpg|png|ttf|eot|woff(2)?)(\?[a-z0-9=&.]+)?$/,
loader: "file-loader",
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.[contenthash].css',
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
}),
new WebpackMd5Hash(),
new CompressionPlugin({
algorithm: 'gzip',
test : /\.js$|\.css$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
threshold: 10240,
minRatio: 0.8
}),
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true,
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
],
},
};
and in my /src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="<%=htmlWebpackPlugin.files.chunks.main.css %>">
<title>Book Ambulance - Stanplus</title>
</head>
<body>
<div id="app">
</div>
<script src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
<script src="<%= htmlWebpackPlugin.files.chunks.vendor.entry %>"></script>
</body>
</html>
you can that those files are loading in browser but page is blank
i have to questions
a) why my app not showing nothing even not any error(may be something in output,splitChunks or in html page).
b) after doing UglifyJsPlugin,CompressionPlugin and dynamically adding routes and import libs/files but also my vendor.js file size is 580 KB,except all these techniques how can i reduce the size of my vendor.js file

Unicode characters not showed when bundling and deploying React app with webpack and babel

On my local environment, unicode characters work fine but when I run webpack to bundle and deploy, string which is like "\uf00c" becomes "" in index_bundle.js
I think there's a problem in babel config or webpack config but I can't resolve it as I'm new to the whole stack.
Here is the codes:
<Label className="switch switch-icon switch-pill switch-success">
<Input type="checkbox" className="switch-input" defaultChecked onChange={this.handleActivateUser.bind(this, user.id)}/>
<span className="switch-label" data-on={'\uf00c'} data-off={'\uf00d'}></span>
<span className="switch-handle"></span>
</Label>
My .babelrc:
{
"presets": [
["env"], ["react"]
],
"plugins": ["transform-class-properties", "transform-object-rest-spread"]
}
And my webpack.config.js:
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const extractCSS = new ExtractTextPlugin('[name].fonts.css');
const extractSCSS = new ExtractTextPlugin('[name].styles.css');
const webpackUglifyJsPlugin = require('webpack-uglify-js-plugin');
var webpack = require('webpack');
config = {
entry: ['babel-polyfill', 'whatwg-fetch', './app/index.js'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(scss)$/,
use: ['css-hot-loader'].concat(extractSCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {alias: {'../img': '../public/img'}}
},
{
loader: 'sass-loader'
}
]
}))
},
{
test: /\.css$/,
use: extractCSS.extract({
fallback: 'style-loader',
use: 'css-loader'
})
},
{
test: /\.(png|jpg|jpeg|gif|ico)$/,
use: [
{
// loader: 'url-loader'
loader: 'file-loader',
options: {
name: './img/[name].[hash].[ext]'
}
}
]
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
// loader: 'url-loader'
loader: 'file-loader',
options: {
name: './fonts/[name].[hash].[ext]'
}
}
]
}
]
},
devServer: {
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
}),
extractCSS,
extractSCSS,
new CopyWebpackPlugin([
{from: './public/img', to: 'img'}
],
{copyUnmodified: false}
)
]
}
module.exports = config;
I found the solution. You need to set your javascript file UTF-8. You could either do
<script type="text/javascript" charset="UTF-8" src="xyz.js"></script>
or
<head>
...
<meta charset="UTF-8">
...
</head>
for all your HTML files
See https://stackoverflow.com/a/44907641/2075423

How to use static js generated throught webpack for react app with es6

I am new to es6/react js and webpack and from last 6 days I am trying to create startkit for react app with es6 and webpack below is my webpack.config.js , I am successfully able to configure web pack dev server. My app is running on http:/localhost:8080/webpack-dev-server. When I am running npm build to generate bundle.js. if I am running my app only using localhost:8080 my app is running in chrome but giving error in mozilla (r.render is not function). Webpack is very confusing... Can we run file bundle.js file locally on file:// server. means like in normal html file if I include that bundle.js file it should work right?
My webpack.config.js
var path = require('path');
var webpack = require('webpack');
//var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
var mainPath = path.join(__dirname, 'app', 'index.js');
var buildPath = path.join(__dirname, 'dist/assets/');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var autoprefixer = require('autoprefixer')
var sassLoaders = [
'css-loader?sourceMap',
'postcss-loader',
'sass-loader?sourceMap&includePaths[]=' + path.join(__dirname, './app')
]
module.exports = {
// Makes sure errors in console map to the correct file
// and line number
devtool: 'cheap-module-source-map',
entry: {
'vendor': ['react','react-dom'],
"bundle":mainPath
},
module: {
loaders: [
{
test: [/\.js$/],
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
},
// //{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
// { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
// { test: /\.s?css$/, loaders: ['style', 'css', 'sass','css?sourceMap', 'sass?sourceMap'] }
{ test: /\.scss$/,
loader: ExtractTextPlugin.extract('style-loader', sassLoaders.join('!'))
},
{ test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, loader: 'file-loader?name=/fonts/[name].[ext]' }
]
},
output: {
// We need to give Webpack a path. It does not actually need it,
// because files are kept in memory in webpack-dev-server, but an
// error will occur if nothing is specified. We use the buildPath
// as that points to where the files will eventually be bundled
// in production
path: buildPath,
filename: '[name].js',
publicPath: 'http://localhost:8080/assets'
},
plugins: [
// Pro-tip: Order matters here.
new ExtractTextPlugin('[name].css'), new webpack.optimize.CommonsChunkPlugin(['bundle', 'vendor'], '[name].js')
],
postcss: [
autoprefixer({
browsers: ['last 2 versions'],
//path: "./dist",
filename: '[name].js',
// Everything related to Webpack should go through a build path,
// localhost:8080/build. That makes proxying easier to handle
publicPath: '/dist/'
})
],
resolve: {
extensions: ['', '.js', '.jsx','.sass','.woff','.ttf','.eot','.svg'],
root: [path.join(__dirname, './app')]
},
watch:true
};
my index.html
<!DOCTYPE html>
<html>
<head>
<title>React Home Page</title>
<link rel="stylesheet" href="assets/bundle.css" />
</head>
<body>
<div id="react-app"></div>
<script type="text/javascript" src="assets/vendor.js"></script>
<script type="text/javascript" src="assets/bundle.js"></script>
</body>
</html>
You don't have to link it yourself, webpack does that for you.
the HtmlWebpackPlugin will make sure that the bundle is being linked in the file that you configure.
var path = require("path");
var HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./app/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index_bundle.js"
},
module: {
rules: [
{ test: /\.(js)$/, use: "babel-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] }
]
},
plugins: [
new HtmlWebpackPlugin({
template: "app/index.html"
})
]
};

Resources