3

I'm having an issue with my webpack.prod.config building my assets properly or possibly an issue with my JS Babel configuration.

I can get it to work with the development build where it is inlining my CSS but it's not working when I try to pull it together into one CSS file for production. Whatever is happening it works in Dev with the CSS being injected directly by import './filename.css' in each of the respective components. It could also be the JS but either way when I build for prod the CSS is not working correctly neither is the JS. All the React JS components and other JS does not display, just the static HTML and CSS styling from the CDN imports. When I click on the URLs in the script tags injected by Webpack they just lead me to the same page instead of the JS or CSS source which I found odd. The JS and CSS looks to be correct in the Build > Static > JS + CSS outputs. Sometime I'm getting a received an text/html MIME when it was supposed to be something else message in the console.

Either that or my JS is broken and not building the page properly. Not in my production build (same build it does when it's deployed to Heroku). I started with a create-react-app (ejected), added Express, preCSS (for SASS like preprocessing), react-bootstrap and a few other things.

This project is sort of a mess as it's something I'm using as a learning tool for a new web dev to transition from using static HTML+CSS into using React, JS and Bootstrap (jQuery is in there temporarily as we convert things together into pure React). It was building previously without issue but since I started to mess with using postCSS + preCSS, but it's no longer working.

Here are some of the main packages/libraries I'm using. - jQuery (CDN Script Tag) - BS3 (CDN Script Tag, .js, .css) - React-Bootstrap - React-Overlays - Babel - postCSS - preCSS - ExtractTextPlugin

Thank you in advance for your help.

HTML + Error I'm getting in the console

[![HTML Being Generated][1]][1] [![Console Error][2]][2]

Webpack.config.prod.js

var path = require('path');
var precss = require('precss');
var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var url = require('url');
var paths = require('./paths');

var homepagePath = require(paths.appPackageJson).homepage;
var publicPath = homepagePath ? url.parse(homepagePath).pathname : '/';
if (!publicPath.endsWith('/')) {
  // Prevents incorrect paths in file-loader
  publicPath += '/';
}

module.exports = {
  bail: true,
  devtool: 'source-map',
  entry: [
    require.resolve('./polyfills'),
    path.join(paths.appSrc, 'index')
  ],
  output: {
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: publicPath
  },
  resolve: {
    extensions: ['', '.js', '.json']
  },
  resolveLoader: {
    root: paths.ownNodeModules,
    moduleTemplates: ['*-loader']
  },
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        include: paths.appSrc
      }
    ],
    loaders: [
      {
        test: /\.js$/,
        include: paths.appSrc,
        loader: 'babel',
        query: require('./babel.prod')
      },
      {
        test: /\.css$/,
        include: [paths.appSrc, paths.appNodeModules],
        // Disable autoprefixer in css-loader itself:
        // https://github.com/webpack/css-loader/issues/281
        // We already have it thanks to postcss.
        loader: ExtractTextPlugin.extract('style', 'css?-autoprefixer!postcss!sass')
      },
      {
        test: /\.json$/,
        include: [paths.appSrc, paths.appNodeModules],
        loader: 'json'
      },
      {
        test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?$/,
        include: [paths.appSrc, paths.appNodeModules],
        loader: 'file',
        query: {
            // name: 'static/media/[name].[hash:8].[ext]'
            name: 'static/media/[name].[ext]'
        }
      },
      {
        test: /\.(mp4|webm)(\?.*)?$/,
        include: [paths.appSrc, paths.appNodeModules],
        loader: 'url',
        query: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      }
    ]
  },
  eslint: {
    // TODO: consider separate config for production,
    // e.g. to enable no-console and no-debugger only in prod.
    configFile: path.join(__dirname, 'eslint.js'),
    useEslintrc: false
  },
  postcss: function() {
    return [precss, autoprefixer];
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
      favicon: paths.appFavicon,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    }),
    new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true,
        warnings: false
      },
      mangle: {
        screw_ie8: true
      },
      output: {
        comments: false,
        screw_ie8: true
      }
    }),
    new ExtractTextPlugin('static/css/[name].[contenthash:8].css')
  ]
};

Build.js

process.env.NODE_ENV = 'production';
var chalk = require('chalk');
var fs = require('fs');
var path = require('path');
var filesize = require('filesize');
var gzipSize = require('gzip-size').sync;
var rimrafSync = require('rimraf').sync;
var webpack = require('webpack');
var config = require('../config/webpack.config.prod');
var paths = require('../config/paths');
var express = require('express');

var app = express();

// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
rimrafSync(paths.appBuild + '/*');

console.log('Creating an optimized production build...');
webpack(config).run(function(err, stats) {
  if (err) {
    console.error('Failed to create a production build. Reason:');
    console.error(err.message || err);
    process.exit(1);
  }

  console.log(chalk.green('Compiled successfully.'));
  console.log();

  console.log('File sizes after gzip:');
  console.log();
  var assets = stats.toJson().assets
    .filter(asset => /\.(js|css)$/.test(asset.name))
    .map(asset => {
      var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
      var size = gzipSize(fileContents);
      return {
        folder: path.join('build', path.dirname(asset.name)),
        name: path.basename(asset.name),
        size: size,
        sizeLabel: filesize(size)
      };
    });
  assets.sort((a, b) => b.size - a.size);

  var longestSizeLabelLength = Math.max.apply(null,
    assets.map(a => a.sizeLabel.length)
  );
  assets.forEach(asset => {
    var sizeLabel = asset.sizeLabel;
    if (sizeLabel.length < longestSizeLabelLength) {
      var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length);
      sizeLabel += rightPadding;
    }
    console.log(
      '  ' + chalk.green(sizeLabel) +
      '  ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
    );
  });
  console.log();

  if (process.env.NODE_ENV === 'production') {
        // Serve the static HTML file from paths.appBuild directory
        app.use(express.static(paths.appBuild));
        console.log('Static build directory now being served, paths.appBuild: ', paths.appBuild);

        // Serve the static HTML file from express
        console.log('Adding static path to Express routing...');
        app.get('*', function(req, res) {
            res.sendFile(paths.appHtml);
            console.log('Path serving HTML at paths.appHTML: ', paths.appHtml);
        });
        // List out which port is being used and listen for changes on the server
        app.listen(process.env.PORT || 9004, function(){
            console.log('Express server listening on port %d in %s mode', (process.env.PORT || 9004), app.settings.env);
        });
    }
  console.log();
});
Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
retrospct
  • 161
  • 1
  • 10
  • 1
    it can be a issue with minify and mangling with webpack, try to avoid minify and us mangle: false in your UglifyJsPlugin and see if it helps. – Jorawar Singh Aug 22 '16 at 21:31
  • Thank you @MrJSingh I figured out what my issue was (after 2.5 days...). Webpack was using the wrong publicPath. It was using an relative path when it should have just been using an absolute path for the assets. Thusly the injected script tags were leading to the wrong path to load them for the css and js. I'll update my original post with the resolution. – retrospct Aug 23 '16 at 00:10
  • 4
    Please answer it so others can benefit? – Dan Abramov Aug 23 '16 at 00:12
  • @DanAbramov updating my original post now. I pressed enter earlier on my first comment on accident. Thanks everyone! – retrospct Aug 23 '16 at 00:14
  • Rather than updating the question - to indicate that it's resolved - could you [create an answer](http://stackoverflow.com/help/self-answer)? – cartant Aug 23 '16 at 02:44

1 Answers1

2

In the webpack.config.prod.js file the output: publicPath: value was set to the variable publicPath which was part of the code ejected from the create-react-app. I reused it without fully understanding what the publicPath variable would resolve to. It was spitting out a path of github-username/repo-name when what I needed was the relative path of '/' for my purposes.

I did not catch that injected css + js tags in the index.html were incorrect paths as the errors reported in the console were only indicating a Syntax error, so it had to be that the CSS and JS were not loading into the page. I corrected the path to use '/' as an relative path to the paths.appBuild folder where all the static assets lived and are served up.

This stack post helped as well: Webpack publicPath

// variables from the create-react-app eject, will be removing these from my webpack.config.prod.js file
var homepagePath = require(paths.appPackageJson).homepage;
var publicPath = homepagePath ? url.parse(homepagePath).pathname : '/';
if (!publicPath.endsWith('/')) {
  // Prevents incorrect paths in file-loader
  publicPath += '/';
}

console.log('homepagePath: ', homepagePath); // Resolves to git repo url
console.log('publicPath: ', publicPath); // Resolves to /retrospct/tellus-aerospace/

module.exports = {
  bail: true,
  devtool: 'source-map',
  entry: [
    require.resolve('./polyfills'),
    path.join(paths.appSrc, 'index')
  ],
  output: {
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: '/' // Previously this was the variable publicPath set above
  },
Community
  • 1
  • 1
retrospct
  • 161
  • 1
  • 10
  • 2
    "It was spitting out a path of github-username/repo-name when what I needed was the relative path of '/' for my purposes." The `publicPath` in CRA is determined by `homepage` you set in `package.json`. What did you set as `homepage`? If you set URL of your deployed website, everything would work. – Dan Abramov Aug 23 '16 at 11:24
  • Dan Abramov hit the nail on the head. In the root object of package.json, setting "homepage": ".", produced src attributes in my production build like I needed for packaging in a war, e.g. src="./static/favicon..." etc. which allowed them to load properly. – J E Carter II Nov 16 '18 at 17:03