amireh / happypack

Happiness in the form of faster webpack build times.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Incremental / partial builds not working (Windows 10.1 + HP V2.2.1 + Webpack V1.13.3)

MrBlenny opened this issue Β· comments

Initial builds works perfectly and is super fast: 10s down form 50s πŸŽ‰πŸŽ‰πŸŽ‰

Webpack watch seems to be broken however. When I make changes to files, webpack does not seem to get notified and therefore incremental builds do not happen. When I disable happypack it works fine.

Webpack config (this is just the base config but should be a good representation). When config.enableHappy is true, incremental builds break - otherwise it works:

import path from 'path';
import webpack from 'webpack';
import HappyPack from 'happypack';

const config = {
  iconPath: 'node_modules/react-icons',
  enableHappy: true          // Toggle this to enable/disable HappyPack
}; 

const getHappyConfig = (enable) => {
  const config = {};
  config.loaders = enable 
    ? [{
      test: /\.jsx?$/,
      loader: 'happypack/loader?id=babel',
      exclude: /node_modules/,
    },{
      test: /(\.js|\.jsx)$/,
      loader: 'happypack/loader?id=babel',
      include: [path.resolve(__dirname, './app/node_modules/react-icons/md')],
    }]
    : [{
      test: /\.jsx?$/,
      loader: 'babel',
      exclude: /node_modules/,
    },{
      test: /(\.js|\.jsx)$/,
      loader: 'babel',
      include: [path.resolve(__dirname, './app/node_modules/react-icons/md')],
    }];

  config.plugins = enable 
    ? [new HappyPack({ threads: 4, id: 'babel', loaders: ['babel']})]
    : [];

  return config;
}

const happyConfig = getHappyConfig(config.enableHappy);

export default {
  module: {
    loaders: [
    ...happyConfig.loaders,
    {
      test: /\.json$/,
      loader: 'json-loader',
    },{
      test: /\.(png|jpg|svg)$/,
      loader: 'url-loader?limit=8192'
    }],
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]/index.js',
    libraryTarget: 'commonjs2',
  },
  resolve: {
    root: [
      path.resolve('./')
    ],
    extensions: ['', '.js', '.jsx'],
    packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
  },
  plugins: [
    ...happyConfig.plugins,
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), // http://stackoverflow.com/questions/25384360/how-to-prevent-moment-js-from-loading-locales-with-webpack
    new webpack.IgnorePlugin(/vertx/),                                 // Ignore vertx so ES6 promise works: https://github.com/stefanpenner/es6-promise/issues/100
  ],
  externals: [{}],
};

Any idea what the issue could be?

Awesome package by the way - the peformance increase on initial build is pretty incredible.

Hey, thanks and I'm glad you like it! πŸ˜„

Honestly the config looks okay to me, but from what I've seen in the past of this "hangs and doesn't rebuild" symptom is that the plugins never marked themselves as finished. Does it work when you do not engage watch mode? Does the process exit?

Also, two things I've no experience with are the plugins you're using; ContextReplacement and Ignore. Try disabling each at a time and see it if yields different results. Let's narrow this down!

Here is the full config - I have disabled all other plugins.

Behavior:

When HappyPack is disabled (watch: false): Webpack runs and watches correctly.
When HappyPack is enabled (watch: false): Webpack runs onces and then the process completes (no files watched).
When HappyPack is disabled (watch: true): Error is thrown (see below)
When HappyPack is enabled (watch: true): Error is thrown (see below)

Error:

\node_modules\webpack-dev-middleware\middleware.js:36
        compiler.plugin("done", function(stats) {
                 ^
TypeError: compiler.plugin is not a function
    at module.exports (\node_modules\webpack-dev-middleware\middleware.js:36:11)

I'm not particularly familiar with webpack - is it normal for it to watch the files even if watch:false is set on the config object? Maybe the issue is related to webpackDevMiddleware

Config:

webpack.config.base.js:

import path from 'path';
import webpack from 'webpack';
import HappyPack from 'happypack';

const config = {
  iconPath: 'node_modules/react-icons',
  enableHappy: false
}; 

const getHappyConfig = (enable) => {
  const config = {};
  config.loaders = enable 
    ? [{
      test: /\.jsx?$/,
      loader: 'happypack/loader?id=babel',
      exclude: /node_modules/,
    },{
      test: /(\.js|\.jsx)$/,
      loader: 'happypack/loader?id=babel',
      include: [path.resolve(__dirname, './app/node_modules/react-icons/md')],
    }]
    : [{
      test: /\.jsx?$/,
      loader: 'babel',
      exclude: /node_modules/,
    },{
      test: /(\.js|\.jsx)$/,
      loader: 'babel',
      include: [path.resolve(__dirname, './app/node_modules/react-icons/md')],
    }];

  config.plugins = enable 
    ? [new HappyPack({ threads: 4, id: 'babel', loaders: ['babel']})]
    : [];

  return config;
}

const happyConfig = getHappyConfig(config.enableHappy);

export default {
  module: {
    loaders: [
    ...happyConfig.loaders,
    {
      test: /\.json$/,
      loader: 'json-loader',
    },{
      test: /\.(png|jpg|svg)$/,
      loader: 'url-loader?limit=8192'
    }],
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]/index.js',
    libraryTarget: 'commonjs2',
  },
  resolve: {
    root: [
      path.resolve('./')
    ],
    extensions: ['', '.js', '.jsx'],
    packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
  },
  plugins: [
    ...happyConfig.plugins,
  ],
  externals: [{}],
};

webpack.config.development.js (this extends the base config):

/* eslint max-len: 0 */
import webpack from 'webpack';
import baseConfig from './webpack.config.base';

const config = {
  ...baseConfig,

  watch: false,
  debug: true,

  devtool: 'cheap-module-eval-source-map',

  entry: {
    main: [
      'babel-polyfill',
      'webpack-hot-middleware/client?path=http://localhost:3001/__webpack_hmr',
      './app/renderer/main/index',
    ],
    menubar: [
      'babel-polyfill',
      'webpack-hot-middleware/client?path=http://localhost:3001/__webpack_hmr',
      './app/renderer/menubar/index',
    ],
    preview: [
      'babel-polyfill',
      'webpack-hot-middleware/client?path=http://localhost:3001/__webpack_hmr',
      './app/renderer/preview/index',
    ],
  },

  output: {
    ...baseConfig.output,
    publicPath: 'http://localhost:3001/dist/',
  },

  module: {
    ...baseConfig.module,
    loaders: [
      ...baseConfig.module.loaders,
    ],
  },

  plugins: [
    ...baseConfig.plugins,
  ],

  target: 'electron-renderer',
};

export default config;

Server.js (this is what actually runs webpack):

/* eslint no-console: 0 */

import express from 'express';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import fs from 'fs';
import config from './webpack.config.development';

const app = express();
const compiler = webpack(config, (err, stats) => {
  /*******************************************
  Write the stats.json for analysis using:
  - http://webpack.github.io/analyse/
  - https://alexkuz.github.io/webpack-chart/
  - https://chrisbateman.github.io/webpack-visualizer/
  ********************************************/
//  fs.writeFileSync('./stats.json', JSON.stringify(stats.toJson()));
});
const PORT = 3001;

app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath,
  stats: {
    colors: true
  }
}));

//app.use(webpackHotMiddleware(compiler));

//app.listen(PORT, 'localhost', err => {
//  if (err) {
//    return;
//  }
//
//  console.log(`Listening at http://localhost:${PORT}`);
//});

I've run into an issue that might be related: with webpack --watch I can't get sass to recompile, but js works fine. The strange thing is that sass won't recompile even if I only use HappyPack for js. No issues when running webpack.

Tested the latest version of HappyPack under both Webpack 1.12.13 and 1.13.3, on macOS Sierra.

Confirming that this is an issue on Windows 7 Enterprise as well. However, I am unable to reproduce it on my Ubuntu box.

Just wanted to mention that I'm on Windows 10 as well, and happypack works fine for me, for both the initial compile and recompiles (ie. incremental compiles).

Windows version: 10.0.14393 Build 14393
Webpack version: 1.14.0
Happypack version: 3.0.3

I don't have time to trim it down (sorry!), but this is my webpack config: (config.js is a-part-of/required-by webpack.config.js)

config.js

const webpack = require("webpack");
const cssnano = require("cssnano");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const config = require("../config");
const debug = require("debug")("app:webpack:config");
var path = require("path");

const paths = config.utils_paths;
const {__DEV__, __PROD__, __TEST__} = config.globals;
const {QUICK_DEPLOY, USE_TSLOADER} = process.env;

debug("Creating configuration.");
const webpackConfig = {
	name: "client",
	target: "web",
	devtool: config.compiler_devtool,
	resolve: {
		root: paths.client(),
		extensions: ["", ".js", ".jsx", ".json"].concat(USE_TSLOADER ? [".ts", ".tsx"] : []),
		fallback: [path.join(__dirname, "node_modules")],
		alias: {
            //"react": __dirname + "/node_modules/react/",
            "react": paths.base() + "/node_modules/react/",
			"react-dom": paths.base() + "/node_modules/react-dom/",
			//"@types/react": paths.base() + "/node_modules/@types/react/",
        }
	},
	// same issue, for loaders like babel
	resolveLoader: {
		fallback: [path.join(__dirname, "node_modules")]
	},
	module: {}
};

if (__PROD__) {
	webpackConfig.module.preLoaders = [
		{test: /\.jsx?$/, loader: "source-map", exclude: /react-hot-loader/}
	];
}

// Entry Points
// ==========

const APP_ENTRY = paths.client(USE_TSLOADER ? "Main.tsx" : "Main.js");

webpackConfig.entry = {
	app: __DEV__
		? [APP_ENTRY].concat(`webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`)
		: [APP_ENTRY],
	//vendor: config.compiler_vendors
};

// Bundle Output
// ==========

webpackConfig.output = {
	filename: `[name].[${config.compiler_hash_type}].js`,
	path: paths.dist(),
	//path: path.resolve(__dirname, "dist"),
	publicPath: config.compiler_public_path,
	pathinfo: true, // include comments next to require-funcs saying path // (this seems to break webpack-runtime-require)
}

// Plugins
// ==========

//let ExposeRequirePlugin = require("webpack-expose-require-plugin");\
var HappyPack = require('happypack');

webpackConfig.plugins = [
	// Plugin to show any webpack warnings and prevent tests from running
	function() {
		let errors = []
		this.plugin("done", function (stats) {
			if (stats.compilation.errors.length) {
				// Log each of the warnings
				stats.compilation.errors.forEach(function (error) {
					errors.push(error.message || error)
				})

				// Pretend no assets were generated. This prevents the tests from running, making it clear that there were warnings.
				//throw new Error(errors)
			}
		})
	},
	new webpack.DefinePlugin(config.globals),
	new HtmlWebpackPlugin({
		//template: paths.client("index.html"),
		template: paths.base("Source/index.html"),
		hash: false,
		// favicon: paths.client("Resources/favicon.ico"), // for including single favicon
		filename: "index.html",
		inject: "body",
		minify: {
			collapseWhitespace: true
		}
	}),
	/*new ExposeRequirePlugin({
		level: "dependency", // "all", "dependency", "application" 
		pathPrefix: "Source", // in case if your source is not placed in root folder. 
	}),*/

	new HappyPack({
		// loaders is the only required parameter:
		//loaders: [ 'babel?presets[]=es2015' ],
		loaders: ["babel"],
	}),

	new webpack.DllReferencePlugin({
		context: path.join(__dirname, "Source"),
		//context: paths.base(),
		manifest: require("../config/dll/vendor-manifest.json")
	}),
]

if (__DEV__) {
	debug("Enable plugins for live development (HMR, NoErrors).")
	webpackConfig.plugins.push(
		new webpack.HotModuleReplacementPlugin(),
		new webpack.NoErrorsPlugin()
	);
} else if (__PROD__ && !QUICK_DEPLOY) {
	debug("Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).")
	webpackConfig.plugins.push(
		new webpack.optimize.OccurrenceOrderPlugin(),
		new webpack.optimize.DedupePlugin(),
		new webpack.optimize.UglifyJsPlugin({
			compress: {
				unused: true,
				dead_code: true,
				warnings: false,
				keep_fnames: true,
			},
			mangle: {
				keep_fnames: true,
			}
		})
	)
}

// Don't split bundles during testing, since we only want import one bundle
/*if (!__TEST__) {
	webpackConfig.plugins.push(
		new webpack.optimize.CommonsChunkPlugin({
			names: ["vendor"]
		})
	)
}*/

// Loaders
// ==========

// JavaScript / JSON
webpackConfig.module.loaders = [
	{
		test: USE_TSLOADER ? /\.(jsx?|tsx?)$/ : /\.jsx?$/,
		//exclude: [/node_modules/, /react-redux-firebase/],
		include: [paths.client()],
		//loader: "babel",
		//loaders: ["happypack/loader"],
		loader: "happypack/loader",
		query: config.compiler_babel
	},
	{
		test: /\.json$/,
		loader: "json"
	},
];
if (USE_TSLOADER) {
	//webpackConfig.module.loaders.push({test: /\.tsx?$/, loader: "awesome-typescript-loader"});
	webpackConfig.module.loaders.push({test: /\.tsx?$/, loader: "ts-loader", query: {include: [paths.client()]}});
}

// Style Loaders
// ==========

// We use cssnano with the postcss loader, so we tell
// css-loader not to duplicate minimization.
const BASE_CSS_LOADER = "css?sourceMap&-minimize"

// Add any packge names here whose styles need to be treated as CSS modules.
// These paths will be combined into a single regex.
const PATHS_TO_TREAT_AS_CSS_MODULES = [
	// "react-toolbox", (example)
]

// If config has CSS modules enabled, treat this project"s styles as CSS modules.
if (config.compiler_css_modules) {
	PATHS_TO_TREAT_AS_CSS_MODULES.push(
		paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, "\\$&") // eslint-disable-line
	)
}
const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join("|")})`)

// Loaders for styles that need to be treated as CSS modules.
if (isUsingCSSModules) {
	const cssModulesLoader = [
		BASE_CSS_LOADER,
		"modules",
		"importLoaders=1",
		"localIdentName=[name]__[local]___[hash:base64:5]"
	].join("&")

	webpackConfig.module.loaders.push({
		test: /\.scss$/,
		include: cssModulesRegex,
		loaders: [
			"style",
			cssModulesLoader,
			"postcss",
			"sass?sourceMap"
		]
	})

	webpackConfig.module.loaders.push({
		test: /\.css$/,
		include: cssModulesRegex,
		loaders: [
			"style",
			cssModulesLoader,
			"postcss"
		]
	})
}

// Loaders for files that should not be treated as CSS modules.
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
webpackConfig.module.loaders.push({
	test: /\.scss$/,
	exclude: excludeCSSModules,
	loaders: [
		"style",
		BASE_CSS_LOADER,
		"postcss",
		"sass?sourceMap"
	]
})
webpackConfig.module.loaders.push({
	test: /\.css$/,
	exclude: excludeCSSModules,
	loaders: [
		"style",
		BASE_CSS_LOADER,
		"postcss"
	]
})

webpackConfig.sassLoader = {
	includePaths: paths.client("styles")
}

webpackConfig.postcss = [
	cssnano({
		autoprefixer: {
			add: true,
			remove: true,
			browsers: ["last 2 versions"]
		},
		discardComments: {
			removeAll: true
		},
		discardUnused: false,
		mergeIdents: false,
		reduceIdents: false,
		safe: true,
		sourcemap: true
	})
]

// File loaders
/* eslint-disable */
webpackConfig.module.loaders.push(
	{ test: /\.woff(\?.*)?$/, loader: "url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff" },
	{ test: /\.woff2(\?.*)?$/, loader: "url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2" },
	{ test: /\.otf(\?.*)?$/, loader: "file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype" },
	{ test: /\.ttf(\?.*)?$/, loader: "url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream" },
	{ test: /\.eot(\?.*)?$/, loader: "file?prefix=fonts/&name=[path][name].[ext]" },
	{ test: /\.svg(\?.*)?$/, loader: "url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml" },
	{ test: /\.(png|jpg)$/, loader: "url?limit=8192" }
)
/* eslint-enable */

// Finalize Configuration
// ==========

// when we don"t know the public path (we know it only when HMR is enabled [in development]) we
// need to use the extractTextPlugin to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
if (!__DEV__ && !__TEST__) {
	debug("Apply ExtractTextPlugin to CSS loaders.")
	webpackConfig.module.loaders.filter((loader) =>
		loader.loaders && loader.loaders.find((name) => /css/.test(name.split("?")[0]))
	).forEach((loader) => {
		const first = loader.loaders[0]
		const rest = loader.loaders.slice(1)
		loader.loader = ExtractTextPlugin.extract(first, rest.join("!"))
		delete loader.loaders
	})

	webpackConfig.plugins.push(
		new ExtractTextPlugin("[name].[contenthash].css", {
			allChunks: true
		})
	)
}

module.exports = webpackConfig;

webpack.config.js

/* eslint key-spacing:0 spaced-comment:0 */
const path = require("path")
const debug = require("debug")("app:config")
const argv = require("yargs").argv
const ip = require("ip")
const environments = require("./environments");

const {NODE_ENV, PORT, USE_TSLOADER, BASENAME} = process.env;

debug("Creating default configuration.")

// Default Configuration
// ==========

const config = {
	env: NODE_ENV || "development",

	// Project Structure
	// ----------
	path_base  : path.resolve(__dirname, ".."),
	dir_client : USE_TSLOADER ? "Source" : "Source_JS",
	dir_dist   : "dist",
	dir_server : "Server",
	dir_test   : "Tests",

	// Server Configuration
	// ----------
	server_host: ip.address(), // use string "localhost" to prevent exposure on local network
	server_port: PORT || 3000,

	// Compiler Configuration
	// ----------
	compiler_babel: {
		cacheDirectory : true,
		/*plugins        : ["transform-runtime", "lodash", "transform-decorators-legacy"],
		presets        : ["es2015", "react", "stage-0"],*/
		plugins        : ["babel-plugin-transform-runtime", "babel-plugin-lodash", "babel-plugin-transform-decorators-legacy"].map(require.resolve),
		presets        : ["babel-preset-es2015", "babel-preset-react", "babel-preset-stage-0"].map(require.resolve),
	},
	//compiler_devtool         : "source-map",
	//compiler_devtool         : "cheap-module-eval-source-map",
	compiler_devtool         : "cheap-module-source-map",
	compiler_hash_type       : "hash",
	compiler_fail_on_warning : false,
	compiler_quiet           : false,
	compiler_public_path     : "/",
	compiler_stats           : {
		chunks : false,
		chunkModules : false,
		colors : true
	},
	/*compiler_vendors: [
		"react",
		"react-router",
		"react-redux",
		"redux",
		"react-google-button",
		"react-modal",
		"moment",
		"radium",
		"react-autobind",
		"react-markdown",
		"react-router-dom",
		"react-router-redux",
		"react-redux-firebase",
		"redux-persist",
		"redux-persist-transform-filter",
		"reselect",
		"react-vmenu",
	],*/

	compiler_css_modules: true, // enable/disable css modules

 	// Test Configuration
	// ----------
	coverage_reporters: [
		{type : "text-summary"},
		{type : "lcov", dir : "coverage"}
	]
};

// All Internal Configuration Below
// Edit at Your Own Risk
// ==========
// ==========

// Environment
// ==========

// N.B.: globals added here must _also_ be added to .eslintrc
config.globals = {
	"process.env": {
		"NODE_ENV": JSON.stringify(config.env)
	},
	"NODE_ENV": config.env,
	"__DEV__": config.env == "development",
	"__PROD__": config.env == "production",
	"__TEST__": config.env == "test",
	"__COVERAGE__": !argv.watch && config.env === "test",
	"__BASENAME__": JSON.stringify(BASENAME || "")
}

// Validate Vendor Dependencies
// ==========

const pkg = require("../package.json")

/*config.compiler_vendors = config.compiler_vendors
	.filter(dep=> {
		if (pkg.dependencies[dep]) return true

		debug(`Package "${dep}" was not found as an npm dependency in package.json; it won't be included in the webpack vendor bundle.`
			+ ` Consider removing it from \`compiler_vendors\` in ~/config/index.js`)
	})*/

// Utilities
// ==========

function base() {
	const args = [config.path_base].concat([].slice.call(arguments))
	return path.resolve.apply(path, args)
}

config.utils_paths = {
	base   : base,
	client : base.bind(null, config.dir_client),
	dist   : base.bind(null, config.dir_dist)
}

// Environment Configuration
// ==========

debug(`Looking for environment overrides for NODE_ENV "${config.env}".`)
const overrides = environments[config.env]
if (overrides) {
	debug("Found overrides, applying to default configuration.")
	Object.assign(config, overrides(config))
} else {
	debug("No environment overrides found, defaults will be used.")
}

module.exports = config;

I know the file above is a mess and a monstrosity. πŸ˜†

I have an excuse though, which is that it's based on a yoeman-generator project, and I've not yet gotten around to understanding/modifying the webpack config file it started with. (I'm updating it to my specific project incrementally)

This should be fixed in 4.0.0-beta.4 - can you try that and confirm please? 🎈

Official release happypack@4.0.0 is out on NPM now and should have the patch for this issue. I'll be closing this but feel free to re-open if the issue pops up again (or open another one.)

Thanks to everyone for troubleshooting.