Short Summary
Is combining htmlwebpackplugin functionality with webpack-dev-middleware impossible because of dev-middleware's reliance on files in memory? Screenshots of script outputs at bottom of this post. Because I've chosen to implement cache-hashed filenames in my production config, I can't seem to use dev-middleware anymore.
My Setup
I have 2 main configurations for my webpack instance. One for development (with hot reload) and one for production. I utilize webpack-merge to create a common.config that I'll eventually extract commonalities between the two configurations (for now it's fairly blank). In terms of app setup I have an API run separately in Python.
The problem
On my production config I'm using splitchunks for vendor/bundle splitting as well as some minimizations. It works perfectly fine. However, when I'm trying to run my development environment, although it's creating the the appropriate bundles for development [i.e. without the hashing] according to the terminal, the index.html file is unable to be found (likely because webpack-dev-middleware looks for things in memory). As a result, I can't see my development environment and I can't see any of the hot reload changes? Previously I would generate all my bundle files with npm run build:production and then use NPM start. I imagine dev-middleware would overlay it's in-memory version of the bundle.js changes over my file on disk, but now that I'm using hashed filenames on prod I can't really do that anymore?
Package.json scripts
"scripts": {
"clean": "rimraf dist/*.{js,css,eot,woff,woff2,svg,ttf,jpg,map,json}",
"build":
"webpack -p --progress --verbose --colors --display-error-details --config webpack/common.config.js",
"build:production": "npm run clean && npm run build",
"flow": "flow",
"lint": "eslint src",
"start": "nodemon bin/server.js",
The relevant parts of server.js
(function initWebpack() {
const webpack = require('webpack');
const webpackConfig = require('./webpack/common.config');
const compiler = webpack(webpackConfig);
app.use(
require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath,
}),
);
app.use(
require('webpack-hot-middleware')(compiler, {
log: console.log,
path: '/__webpack_hmr',
heartbeat: 10 * 1000,
}),
);
app.use(express.static(path.join(__dirname, '/')));
})();
app.get(/.*/, (req, res) => {
res.sendFile(path.join(__dirname, '/dist/index.html'));
});
common.config.js
const path = require('path');
const merge = require('webpack-merge');
// const HtmlWebpackPlugin = require('html-webpack-plugin');
const development = require('./dev.config');
const production = require('./prod.config');
const TARGET = process.env.npm_lifecycle_event;
const PATHS = {
app: path.join(__dirname, '../src'),
build: path.join(__dirname, '../dist'),
nodeModulesDir: path.join(__dirname, 'node_modules'),
indexFile: path.join(__dirname, './src/index'),
};
process.env.BABEL_ENV = TARGET;
const common = {
entry: [PATHS.app],
output: {
path: PATHS.build,
},
};
if (TARGET === 'start' || !TARGET) {
module.exports = merge(development, common);
}
if (TARGET === 'build' || !TARGET) {
module.exports = merge(production, common);
}
dev.config.js
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
require('babel-polyfill').default;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PATHS = {
app: path.join(__dirname, '../src'),
};
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: ['webpack-hot-middleware/client', './src/index'],
mode: 'development',
output: {
publicPath: '/dist/',
},
resolve: {
extensions: ['.jsx', '.js', '.json', '.scss', '.less'],
modules: ['node_modules', PATHS.app],
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
'postcss-loader',
],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
'sass-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
{
loader: 'less-loader',
options: {
//addlater
},
},
],
},
{
test: /bootstrap-sass\/assets\/javascripts\//,
use: [
{
loader: 'imports-loader',
options: {
jQuery: 'jquery',
},
},
],
},
{
test: require.resolve('jquery'),
use: [
{
loader: 'expose-loader',
options: '$',
},
{
loader: 'expose-loader',
options: 'jQuery',
},
],
},
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 50000,
mimetype: 'application/font-woff',
},
},
],
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
},
__DEVELOPMENT__: true,
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.ProvidePlugin({
jQuery: 'jquery',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index_template.html',
}),
],
};
Here's an example of what my index.html file looks like after using npm run build:production. As you can see, the in-memory version of index.html probably can't work with this anymore with the hashed filenames?
<link href="/dist/vendor.a85f.css" rel="stylesheet"><link href="/dist/main.4d1e.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/dist/manifest.81a7.js"></script><script type="text/javascript" src="/dist/vendor.99aa.js"></script><script type="text/javascript" src="/dist/main.6eb4.js"></script></body>
Other notes:
On latest version of webpack 4.
My production version works fine
Any help much appreciated.
UPDATE:
I've swapped out rimraf dist for clean webpack plugin and moved it to my common.config. That way on each build it's doing the clean before generating index.html. However, I've noticed that when I use npm start, while the output in terminal is showing that files are emitted....I can't find them anywhere? After investigated webpack-dev-middleware, it seems they store things in memory. This is probably the core problem. How can I tie htmlwebpack plugin together with something like dev-middleware if it's in memory or perhaps I need to maintain a separate index.html file? I'm guessing the reason why this flow worked previously was because I had static names for bundle.js for both prod and dev so the in-memory version had no problem. now that the names are hashed from the prod version...it doesn't know what to do?
Related
I want to load images directly from HTML with Webpack 4 and add custom Javascript files to my HTML file but both files inspected at console show Not found 404.
How to properly load images and Javascipt files with Webpack 4?
My Webpack 4 config file:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
main: "./src/index.js"
},
output: {
path: path.join(__dirname, "../build"),
filename: "[name].bundle.js"
},
mode: "development",
devServer: {
contentBase: path.join(__dirname, "../build"),
compress: true,
port: 3000,
overlay: true
},
devtool: "cheap-module-eval-source-map",
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader" // transpiling our JavaScript files using Babel and webpack
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
"style-loader", // creates style nodes from JS strings
"css-loader", // translates CSS into CommonJS
"postcss-loader", // Loader for webpack to process CSS with PostCSS
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: [
{
loader: "file-loader", // This will resolves import/require() on a file into a url and emits the file into the output directory.
options: {
name: "[name].[ext]",
outputPath: "assets",
}
},
]
},
{
test: /\.html$/,
use: {
loader: "html-loader",
options: {
attrs: ["img:src", ":data-src"],
minimize: true
}
}
}
]
},
plugins: [
// CleanWebpackPlugin will do some clean up/remove folder before build
// In this case, this plugin will remove 'dist' and 'build' folder before re-build again
new CleanWebpackPlugin(),
// The plugin will generate an HTML5 file for you that includes all your webpack bundles in the body using script tags
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html"
}),
]
My webpack.prod.js file:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const TerserJSPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-
plugin");
const BrotliPlugin = require("brotli-webpack-plugin");
const PurgecssPlugin = require('purgecss-webpack-plugin');
const glob = require("glob");
module.exports = {
entry: {
main: "./src/index.js"
},
output: {
path: path.join(__dirname, "../build"),
filename: "[name].[chunkhash:8].bundle.js",
chunkFilename: "[name].[chunkhash:8].chunk.js"
},
mode: "production",
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader" // transpiling our JavaScript files using
Babel and webpack
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader", // translates CSS into CommonJS
"postcss-loader", // Loader for webpack to process CSS with
PostCSS
"sass-loader" // compiles Sass to CSS, using Node Sass by
default
]
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: [
{
loader: "file-loader", // This will resolves import/require()
on a file into a url and emits the file into the output directory.
options: {
name: "[name].[ext]",
outputPath: "assets/"
}
},
]
},
{
test: /\.html$/,
use: {
loader: "html-loader",
options: {
attrs: ["img:src", ":data-src"],
minimize: true
}
}
}
]
},
optimization: {
minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()],
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
},
chunks: "all"
},
runtimeChunk: {
name: "runtime"
}
},
plugins: [
// CleanWebpackPlugin will do some clean up/remove folder before build
// In this case, this plugin will remove 'dist' and 'build' folder
before re-build again
new CleanWebpackPlugin(),
// PurgecssPlugin will remove unused CSS
new PurgecssPlugin({
paths: glob.sync(path.resolve(__dirname, '../src/**/*'), { nodir:
true })
}),
// This plugin will extract all css to one file
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].bundle.css",
chunkFilename: "[name].[chunkhash:8].chunk.css",
}),
// The plugin will generate an HTML5 file for you that includes all
your webpack bundles in the body using script tags
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html"
}),
My project nesting:
--Build
--src
----html
----js
----styles
----assets
------images
I want that files would load simply:
<img src="assets/images/myimage.jpg">
<srcipt src="js/custom.js"></script>
Any help would be appreciated.
Ironically this is my second project with Webpack 4 and this time I can't fix this issue, the first time there was no problem.
This is how img work in Webpack:
<img src=require("./assets/images/myimage.jpg")>
if you use "require" with file-loader, you need to add default.
<img src=require("./assets/images/myimage.jpg").default>
For script tag, src should be your output bundle.js file; because Webpack writes all the code into the bundle.js with the help of Babel. So when browser request html, browser will have only one Javascript file which is the bundle to download. That is the whole point of bundle, smaller bundle.js is better for performance.
// According to your Webpack config, it is main.bundle.js
<srcipt src="main.bundle.js"></script>
but in order to browser to use this main.bundle.js, it has to be publicly available. So you need express to define the public folder, when browser looks for main.bundle.js, you app will be looking into the public folder and if that file exists, it will ship it to the browser.
const express = require("express");
const server = express();
const staticMiddleware = express.static("build");
server.use(staticMiddleware);
I am moving a web app to react, therefore and moving from Grunt as a buildtool over to webpack. Right now, the below code is the webpack.config file. This is set up as recommended for developing and then has a build script (npm run and npm build)
However, the build script now only concatenates the components/react js files and puts them at the root of the dist folder. No other files are copied over. I don't understand the point of the build script if that's all it does. But I need to be able to add that in, however, no resource with reacts build scripts shows how you would go about that
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./app/src/components/app.js",
output: {
path: path.join(__dirname, "/dist"),
filename: "index_bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./app/index.html"
})
]
};
you do not need a '/'
output: {
path: path.join(__dirname, "dist"),
filename: "index_bundle.js"
},
I have a react App for which I'd like to run its mocha specs (unit-tests) in a browser. I found this SO post and tried to apply same idea to my project. I came up with the following webpack config file:
webpack.config.test.js
const nodeExternals = require('webpack-node-externals');
const path = require('path');
const host = 'localhost';
const port = '8084';
module.exports = {
target: 'web',
externals: [nodeExternals()],
entry: './specs/index.js',
output: {
filename: 'debug.bundle.js',
path: path.join(__dirname, 'tests'),
publicPath: `http://${host}:${port}/tests`,
},
devServer: {
host,
port,
},
module: {
rules: [
{
test: /\.js$/,
loaders: ['react-hot-loader', 'babel-loader'],
enforce: 'pre',
},
{
test: /.+Spec\.js$/,
loaders: ['mocha-loader'],
},
{
test: /(\.css|\.scss)$/,
loader: 'null-loader',
exclude: [
/build/,
],
},
{
test: /(\.jpg|\.jpeg|\.png|\.gif)$/,
loader: 'null-loader',
},
],
},
};
And, after starting the server with:
webpack-dev-server --config webpack.config.test.js
I get the following error in console:
I've read that the problem might be with webpack-node-externals but not really sure what's happening. Any ideas?
I think you will want to use webpack-node-externals only when you bundle files for backend (as described in plugin README). When you use it you forbid it to build all modules from node_modules folder.
https://babeljs.io/docs/usage/polyfill/#usage-in-browser
I did not understand the lines on the documentation page under:
Usage in Browser heading
can someone help me with what else is required:
Below are my code snippets:
I'm using storybook as a boilerplate:
webpack.config.js file:
entry: [
'babel-polyfill',
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs
]
index.js file:
import 'babel-polyfill';
import React from 'react';
Is there some other files also where I need to add babel-polyfill related code.
require('babel-polyfill');
var path = require('path');
var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
var getClientEnvironment = require('./env');
var paths = require('./paths');
var publicPath = '/';
var publicUrl = '';
var env = getClientEnvironment(publicUrl);
module.exports = {
devtool: 'cheap-module-source-map',
entry: ['babel-polyfill',
require.resolve('react-dev-utils/webpackHotDevClient'),
require.resolve('./polyfills'),
paths.appIndexJs
],
output: {
path: paths.appBuild,
pathinfo: true,
filename: 'static/js/bundle.js',
publicPath: publicPath
},
resolve: {
fallback: paths.nodePaths,
extensions: ['.js', '.json', '.jsx', ''],
alias: {
'react-native': 'react-native-web'
}
},
module: {
// First, run the linter.
// It's important to do this before Babel processes the JS.
preLoaders: [{
test: /\.(js|jsx)$/,
loader: 'eslint',
include: paths.appSrc,
}],
loaders: [{
exclude: [/\.html$/, /\.(js|jsx)$/, /\.css$/, /\.json$/],
loader: 'url',
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
// Process JS with Babel.
{
test: /\.(js|jsx)$/,
include: paths.appSrc,
loader: 'babel',
query: {
cacheDirectory: true
}
}, {
test: /\.css$/,
loader: 'style!css?importLoaders=1!postcss'
}, {
test: /\.json$/,
loader: 'json'
}
]
},
// We use PostCSS for autoprefixing only.
postcss: function() {
return [
autoprefixer({
browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // React doesn't support IE8 anyway
]
}),
];
},
plugins: [
new InterpolateHtmlPlugin({
PUBLIC_URL: publicUrl
}),
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
new webpack.DefinePlugin(env),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
],
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
There are two ways to get this code into your browser.
1 - Include the babel-polyfill module in the webpack bundle
2 - Load it as an external script in your html
Webpack - adding bundle dependencies with entry arrays
Put an array as the entry point to make the babel-polyfill module available to your bundle as an export.
With webpack.config.js, add babel-polyfill to your entry array.
The webpack docs explain how an entry array is handled:
What happens when you pass an array to entry? Passing an array of file
paths to the entry property creates what is known as a "multi-main
entry". This is useful when you would like to inject multiple
dependent files together and graph their dependencies into one
"chunk".
Webpack.config.js
require("babel-polyfill");
var config = {
devtool: 'cheap-module-eval-source-map',
entry: {
main: [
// configuration for babel6
['babel-polyfill', './src/js/main.js']
]
},
}
Alternative to Webpack - load babel-polyfill as an external script in the browser html
The alternative to using webpack would mean including the module as an external script in your html. It will then be available to code in the browser but the webpack bundle won't be directly aware of it.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.22.0/polyfill.js"></script>
I recently changed the name of my root directory (where package.json and webpack.config.js sits) and now webpack-dev-server is not updating anytime I change my files.
Here's my webpack config:
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');
module.exports = {
context: path.join(__dirname, "src"),
devtool: debug ? "inline-sourcemap" : null,
entry: "./js/init.js",
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015', 'stage-0'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
}
}
]
},
output: {
path: __dirname + "/src/",
filename: "app.js"
},
plugins: debug ? [] : [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
],
devServer: {
port: 3000,
hot: true,
historyApiFallback: {
index: 'index.html'
}
}
};
And my directory looks like this (Client React is the folder that had its name changed):
Let me clarify that this worked fine before, so I really have no idea why this isn't working now.
Edit: Scripts in package.json
"scripts": {
"dev": "./node_modules/.bin/webpack-dev-server --content-base src --inline --hot",
"build": "webpack"
},
You have to remove the brackets. Probably because they are not properly escaped by the watch module that webpack uses (watchpack) or the part that does the final watching in the System itself. I recommend you don't use any special characters inside directory- or filenames because of such bugs.