I have 3 config JavaScript files like config.local.js, config.dev.js, config.prod.js. An example of one of these files.
// config.dev.js
function Config(){
return{
apiUrl: "https://myapi.dev.com/"
}
}
module.exports = Config;
Now I have an entry file that requires this config file. For instance:
// register.js
function Register(){
const config = require("../config");
// rest of the code to use that config
}
My web pack config looks like this.
module.exports = {
entry: {
register: "./app/src/register.js",
},
output: {
filename: "[name].js",
path: __dirname + "/app/dist",
},
};
I would like that when I run web pack for production (web pack --mode=production), it would bundle the config.prod.js file.
Is this possible?
See this page: https://webpack.js.org/guides/dependency-management/
require('../config.' + process.env.NODE_ENV)
Related
I'm using revealjs to create responsive presentations. The problem with revealjs is that all the slides code is written in a single HTML file which can be messy to some level (Some presentations' HTML code reached about 3500 lines of HTML in that single file).
I'm now restructuring this system and I would like to have a directory named slides that contains each slide HTML file. Each of these files is named slidenumber.html. Finally, I want to bundle all of the files with webpack 5 into a single HTML file in dist. I managed to achieve this but it has an issue with the dev server.
webpack.config.js
// ... imports ....
module.exports = {
...,
plugins: [
....,
new HtmlWebpackPlugin({
filename: "index.html",
inject: true,
templateContent: getTemplate(),
}),
new WatchExternalFilesPlugin({
files: ["./slides/*.html"],
}),
],
module: {
rules: [...],
},
devServer: {
port: 8080,
},
};
The getTemplate function loops over the HTML files in the slides directory and returns them wrapped with the template boilerplate
This is the function for reference
getTemplate.js
const fs = require("fs/promises");
const path = require("path");
const { parse } = require("node-html-parser");
module.exports = async () => {
const template = parse(
await fs.readFile(path.join(__dirname, "../templates/index.html"))
);
const files = await fs.readdir(path.join(__dirname, "../slides"));
for await (const fileName of files) {
const slide = parse(
await fs.readFile(path.join(__dirname, `../slides/${fileName}`))
);
template.querySelector("#slides").appendChild(slide);
}
return template.toString();
};
all of the above code is working fine on build but when running the dev server, I can't get the HtmlWebpackPlugin to re-execute the templateContent: getTemplate() on the change of any HTML slide file in the slides directory and as a result, when I edit any file of the slides HTML files in the slides directory, I don't get any update.
I'm aware that templateContent is supposed to run only on the start of the server but I'm asking if there is any other feature that can get me to the required behavior.
Thanks if you made it to here and excuse my English, I'm not a native speaker.
I could achieve the behavior I described in the question by setting a middleware from the dev server options that catches any request and returns the output of the getTemplate function.
This is the configurations for the dev server
webpack.config.dev.js
// ...imports...
module.exports = {
mode: "development",
entry: { index: "./main.js" },
output: {...},
module: {
rules: [...],
},
devServer: {
port: 8080,
watchFiles: ["./slides/*.html"],
hot: true,
onBeforeSetupMiddleware: function (devServer) {
devServer.app.get("/", async function (req, res) {
res.send(await getTemplate());
});
},
},
};
These are the configurations for the production server
webpack.config.production.js
// ...imports...
module.exports = {
mode: "production",
entry: { index: "./main.js" },
output: {...},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
templateContent: getTemplate(),
inject: false,
}),
],
module: {
rules: [...],
},
};
I used the webpackHtmlPlugin in production as usual but in development, I didn't use it at all since it can't reload the templates on the build
In development, though I lost the ability to add the hash number to the compiled js file as I won't be able to predict the hash and inject its script tag. The compiled file had the same name as the original file and I added the script tag manually in the HTML template file.
Hope this helps anyone!
As per the documentation, I am not using worker-loader, I am trying to use the native way suggested by the webpack-5 documentation.
Below is the usage of the worker script in the main thread.
const worker = new window.Worker(
new URL("../workers/listOperation.worker.js", import.meta.url),
{
type: "module",
},
);
worker.postMessage({ list: hugeList, params: reqData });
worker.onerror = err => console.error(err);
worker.onmessage = e => {
const { list } = e.data;
// Usage of `list` from the response
worker.terminate();
};
return worker;
It works fine if there are no imports used in the script. But when I import any node modules (e.g. loadash/get) or any other function/constants from local, it does not work as the output webWorker bundle file doesn't transpile and bundle the imported code. It keeps the "import" statement as it is.
Below is the worker script (listOperation.worker.js)
import get from "lodash/get";
import { ANY_CONSTANT } from "../constants"; // This is some local constant
addEventListener("message", e => {
const { list, params } = e.data;
// Here I have some usage of `get` method from `lodash/get` and ANY_CONSTANT
self.postMessage({
list: list,
});
});
Webpack outputs the bundle file like below which won't be usable by the browser, if I put the /\.worker.js$/ pattern in the the exclude of babel-loader rule.
import get from "lodash/get";import{ANY_CONSTANT}from"../constants";addEventListener("message",(e=>{const{list:s,params:t,.......
And even if I don't put the /\.worker.js$/ pattern in the the exclude of babel-loader rule, the output bundle still doesn't include the implementation of get from lodash/get or the value of the constant. It just outputs it in cjs using require.
Also, I made use of asset module so that I can put the output file inside a directory, not directly in the root of the dist folder. Configuration changes in my webpack.config looks like this.
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.js$/,
exclude: [/(node_modules)/, /\.worker.js$/],
use: {
loader: "babel-loader", // This uses the config defined in babel.config.js
},
},
{
test: /\.worker.js$/,
exclude: /(node_modules)/,
type: "asset/resource",
generator: {
filename: "js/workers/[hash][ext][query]",
},
},
],
},
}
I am currently using manual reloads with npx webpack which are time consuming.
So, attempting to implement HMR using Webpack docs and this tutorial
It does not throw any errors when I run npm start, tells me that files were generated but the files are not available in the destination directory /dist. If they are placed somewhere else, I could not find.
Also, the HMR as such seems to be working in the sense that it automatically recompiles once changes are made to the app files.
It automatically opens localhost:8080 as well, but shows only the directory list or a message like CANNOT GET.
By the way, I do not use Symfony Encore.
The directory looks like this:
- bin
- client // where the React files are
- config // Symfony
- node_modules
- public
- dist // here goes Webpack's output ( normally )
- index.php
- src // Symfony app files
- templates - Twig
- var
- vendor
webpack.config.js
The above webpack.config.js looks like below, in the latest config attempt
const webpack = require('webpack');
const { InjectManifest } = require('workbox-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const path = require('path');
const useDevServer = true;
const publicPath = useDevServer ? 'http://localhost:8080/public/dist/' : '/dist/';
console.log('path', path.resolve(__dirname, 'public/dist'));
console.log('public path', publicPath);
module.exports = {
mode: 'development',
entry: './client/index.js',
output: {
path: path.resolve(__dirname, 'public/dist'),
filename: '[name].[hash].js',
publicPath,
},
devtool: 'inline-source-map',
module: {
....
},
devServer: {
// contentBase: './dist',
hot: true,
allowedHosts: [
'http://127.0.0.1:8000',
'http://127.0.0.1:8001',
'http://localhost:8000',
'http://localhost:8001',
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
I have a rather large React application built with webpack 2. The application is embedded into a Drupal site as a SPA within the existing site. The Drupal site has a complex gulp build setup and I can't replicate it with webpack, so I decided to keep it.
I have split my React application into multiple parts using the DllPlugin / DllReferencePlugin combo which is shipped out of the box in webpack 2. This works great, and I get a nice vendor-bundle when building with webpack.
The problem is when I try to run my webpack configuration in gulp, I get an error. I might be doing it wrong, as I have not been able to find much documentation on this approach, but nevertheless, it's not working for me.
It looks like it's trying to include the the manifest file from my vendor-bundle before creating it.
Whenever I run one of my defined gulp tasks, like gulp react-vendor I get an error, saying that it cannot resolve the vendor-manifest.json file.
If I on other hand run webpack --config=webpack.dll.js in my terminal, webpack compiles just fine and with no errors.
I have included what I think is the relevant files. Any help on this is appreciated.
webpack.config.js
// Use node.js built-in path module to avoid path issues across platforms.
const path = require('path');
const webpack = require('webpack');
// Set environment variable.
const production = process.env.NODE_ENV === "production";
const appSource = path.join(__dirname, 'react/src/');
const buildPath = path.join(__dirname, 'react/build/');
const ReactConfig = {
entry: [
'./react/src/index.jsx'
],
output: {
path: buildPath,
publicPath: buildPath,
filename: 'app.js'
},
module: {
rules: [
{
exclude: /(node_modules)/,
use: {
loader: "babel-loader?cacheDirectory=true",
options: {
presets: ["react", "es2015", "stage-0"]
},
},
},
],
},
resolve: {
modules: [
path.join(__dirname, 'node_modules'),
'./react/src/'
],
extensions: ['.js', '.jsx', '.es6'],
},
context: __dirname,
devServer: {
historyApiFallback: true,
contentBase: appSource
},
// TODO: Split plugins based on prod and dev builds.
plugins: [
new webpack.DllReferencePlugin({
context: path.join(__dirname, "react", "src"),
manifest: require(path.join(__dirname, "react", "vendors", "vendor-manifest.json"))
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
filename: 'webpack-loader.js'
}),
]
};
// Add environment specific configuration.
if (production) {
ReactConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin()
);
}
module.exports = [ReactConfig];
webpack.dll.js
const path = require("path");
const webpack = require("webpack");
const production = process.env.NODE_ENV === "production";
const DllConfig = {
entry: {
vendor: [path.join(__dirname, "react", "vendors", "vendors.js")]
},
output: {
path: path.join(__dirname, "react", "vendors"),
filename: "dll.[name].js",
library: "[name]"
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, "react", "vendors", "[name]-manifest.json"),
name: "[name]",
context: path.resolve(__dirname, "react", "src")
}),
// Resolve warning message related to the 'fetch' node_module.
new webpack.IgnorePlugin(/\/iconv-loader$/),
],
resolve: {
modules: [
path.join(__dirname, 'node_modules'),
],
extensions: ['.js', '.jsx', '.es6'],
},
// Added to resolve a dependency issue in this build #https://github.com/hapijs/joi/issues/665
node: {
net: 'empty',
tls: 'empty',
dns: 'empty'
}
};
if (production) {
DllConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin()
);
}
module.exports = [DllConfig];
vendors.js (to determine what to add to the Dll)
require("react");
require("react-bootstrap");
require("react-dom");
require("react-redux");
require("react-router-dom");
require("redux");
require("redux-form");
require("redux-promise");
require("redux-thunk");
require("classnames");
require("whatwg-fetch");
require("fetch");
require("prop-types");
require("url");
require("validator");
gulpfile.js
'use strict';
const gulp = require('gulp');
const webpack = require ('webpack');
const reactConfig = require('./webpack.config.js');
const vendorConfig = require('./webpack.dll.js');
// React webpack source build.
gulp.task('react-src', function (callback) {
webpack(reactConfig, function (err, stats) {
callback();
})
});
// React webpack vendor build.
gulp.task('react-vendor', function (callback) {
webpack(vendorConfig, function (err, stats) {
callback();
})
});
// Full webpack react build.
gulp.task('react-full', ['react-vendor', 'react-src']);
NOTE:
If I build my vendor-bundle with the terminal with webpack --config=webpack.dll.js first and it creates the vendor-manifest.json file, I can then subsequently successfully run my gulp tasks with no issues.
This is not very helpful though, as this still will not allow me to use webpack with gulp, as I intend to clean the build before new builds run.
I ended up using the solution mentioned in the end of my question. I build my DLL file first and then I can successfully run my gulp webpack tasks.
One change that can make it easier to debug the issue, is to use the Gulp utility module (gulp-util) to show any webpack errors that might show up during build of webpack, using gulp.
My final gulp setup ended up looking like this:
gulpfile.js
'use strict';
const gulp = require('gulp');
const gutil = require('gulp-util');
const webpack = require('webpack');
const reactConfig = require('./webpack.config.js');
const vendorConfig = require('./webpack.dll.js');
// React webpack source build.
gulp.task('react', function (callback) {
webpack(reactConfig, function (err, stats) {
if (err) {
throw new gutil.PluginError('webpack', err);
}
else {
gutil.log('[webpack]', stats.toString());
}
callback();
});
});
// React webpack vendor build.
gulp.task('react-vendor', function (callback) {
webpack(vendorConfig, function (err, stats) {
if (err) {
throw new gutil.PluginError('webpack', err);
}
else {
gutil.log('[webpack]', stats.toString());
}
callback();
});
});
// React: Rebuilds both source and vendor in the right order.
gulp.task('react-full', ['react-vendor'], function () {
gulp.start('react');
});
I hope this might help someone in a similar situation.
Whenever I run one of my defined gulp tasks, like gulp react-vendor I get an error, saying that it cannot resolve the vendor-manifest.json file.
Your gulpfile.js contains this:
const reactConfig = require('./webpack.config.js');
const vendorConfig = require('./webpack.dll.js');
And webpack.config.js contains this:
new webpack.DllReferencePlugin({
context: path.join(__dirname, "react", "src"),
manifest: require(path.join(__dirname, "react", "vendors", "vendor-manifest.json"))
}),
The require() calls are currently all executed immediately. Whenever you run Gulp, it will evaluate both Webpack configuration files. As currently configured, Node runs the code in webpack.config.js at startup, and from there it sees the require() used in your creation of the DllReferencePlugin, so it will also try to read manifest.json at that time and turn it into an object...which is before it has been built.
You can solve this in one of two ways:
The DllReferencePlugin's manifest option supports either an object (which is what you are currently providing), or else a string containing the path of the manifest file. In other words, it should work if you remove the require() from around your path.join(...) call.
Alternatively, you can also defer the loading of the Webpack config files. Moving your const reactConfig = require('./webpack.config.js'); from the top of the file directly into the gulp task function should be sufficient, assuming that this function is not invoked until after the manifest has been built.
I am calling webpack programmatically. At the time that I call it, I have a settings object that I would like to include as a module in webpack. Is this possible?
I'm looking for something similar to the DefinePlugin, but I would like to define a module, not free variables.
My app code,app.js, looks like this:
var settings = require('settings');
console.log('Build number is', settings.buildNumber);
My webpack runner, webpack-runner.js:
var settings = {
buildNumber: 100
};
// Can I pass settings into webpack config such that
// app.js will be able to access it with require('settings')?
var config = {
entry: "./app.js",
output: {
path: __dirname,
filename: "build.js"
}
};
webpack(config, function(err, stats) {
console.log(stats.toString());
});
Currently, the only way I have found to do this is to save my settings to a file, and then set an alias to the path of the file. But I would like to avoid saving a file only to have webpack open it a moment later.
Yes, just require in your settings file. Example below.
// settings.js
module.exports = {
buildNumber: 100
};
// webpack.config.js
var settings = require('./settings'); // settings.buildNumber => 100
var config = {
entry: './entry.js',
output: {
path: __dirname,
filename: 'build.js'
}
};
webpack(config, function( err, stats ) {
console.log(stats.toString({
colors: true,
modules: true,
chunkModules: true
}));
});