Bundle splitting with Webpack 4 - duplicate vendor bundle - reactjs

We have a legacy multipage application where each page has its own js file. We use Webpack 4 and now we start to use react as well. I want to separate react bundle from our own code. So each page loads its own legacy pageXJS.js file, the new pageXReact.js file, and the react.bundle.js
We have a webpack config below that works almost fine.
const path = require('path');
const webpack = require('webpack');
const jsModuleFolder = './src/main/resources/static/js/module/';
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
'pageA/pageAJS': jsModuleFolder + 'pageA/pageAJS.js',
'pageB/pageBJS': jsModuleFolder + 'pageB/pageBJS.js',
'pageC/pageCJS': jsModuleFolder + 'pageC/pageCJS.js',
'pageC/PageCReact': jsModuleFolder + 'pageC/pageCReact.js',
vendor: ['react', 'react-dom']
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, jsModuleFolder )
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
chunks: 'initial',
test: 'vendor',
name: '../react/react',
enforce: true
}
},
},
},
};
It generates the react.bundle.js in the react folder. However, it also generates a vendor.bundle.js file in the module folder. The vendor.bundle.js is just a webpackBoostrap file without real content. The webpackBootstrap is also part of the generated PageCReact.bundle.js so the vendor.bundle.js seems to be unnecessary. (And my site works fine without it.)
How can I prevent this file from being generated as well?

It's the runtime file and is needed by webpack.
You can include the file in your entry point file like this:
optimization: {
runtimeChunk: false // <-- set to false
I would suggest to configure it like this:
optimization: {
runtimeChunk: 'single', // creates a single runtime chunk for all entry points.

Related

Module Federation, React, and Apollo 3 warnings

I'm building an app with micro-frontends using webpack 5's module federation plugin. Everything was fine until I started adding react hooks into my remote app. At that point I received errors about "invalid usage of hooks", i.e. I discovered I had TWO versions of react loaded, one from the remote and one from the app consuming the remote.
That problem was solved by adding a shared key to the ModuleFederationPlugin section of my webpack config that marked React as a singleton. Now everything compiles and seems to run just fine.
However, the webpack compiler is throwing some annoying warnings at me now. Its saying:
No required version specified and unable to automatically determine one. Unable to find required version for "react" in description file (/Users/myComputer/Development/myapp/node_modules/#apollo/client/react/context/package.json). It need to be in dependencies, devDependencies or peerDependencies.
Here is what my webpack config (in the remote) looks like currently:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const deps = require('./package.json').dependencies
module.exports = {
mode: 'development',
devServer: { port: 3001 },
entry: './src/index.tsx',
output: {
path: __dirname + '/dist/',
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
use: 'ts-loader',
},
]
},
devtool: 'source-map',
plugins: [
new ModuleFederationPlugin(
{
name: 'myRemote',
filename: 'remoteEntry.js',
exposes: {
'./App':
'./src/App/App.tsx',
},
shared: {
'react': {
singleton: true,
version: deps['react'],
},
'react-dom': {
singleton: true,
version: deps['react-dom'],
},
},
}
),
new HtmlWebpackPlugin({
template:
'./index.html',
})
]
}
The consuming app's webpack config is almost the same, especially the shared section (there are some slight differences in that it declares the remotes).
What would be the way to tell webpack that the apollo package will be getting its react dependency from somewhere else? Or if thats not the right thing to tell webpack, what is and how can I get rid of these warnings?
Fixed my own problem by changing the key version to requiredVersion

Webpack build for exisitng AngularJS project

I am looking to migrate the existing Ruby based build system in our AngularJS(1.4.X) project to Webpack. The project code is not using JS modules and being with old-school Angular code patter I am not sure how Webpack will find all the controller and factory files in the project.
Folder structure is like,
-app
- assets
- javascripts
- ctrl
- controllerA.js
- controllerB.js
-services
-serviceA.js
-serviceB.js
- angular.min.js
- angular-route.js
- main.js
Wen I use the main.js in my entry point it get copied into the build folder but none of the other files as processed by Webpack even if I use babel-loader to .js rule.
One option I can think of is to use all other files into a separate bundle file using something like
https://www.npmjs.com/package/webpack-merge-and-include-globally, but I want to know whether there is a better way of doing it.
My current webpack config is as below.
module.exports = {
context: __dirname +'/app',
entry: {
'app-portal': [
'/assets/javascripts/main.js',
'/assets/javascripts/angular.min.js',
'/assets/stylesheets/portal/style.css',
'/assets/stylesheets/portal/navbar.css',
'/assets/stylesheets/portal/animation.css',
'/assets/stylesheets/portal/bootstrap.min.css',
'/assets/stylesheets/portal/bootstrap-notify.css',
'/assets/stylesheets/portal/fontello.css',
]
},
output: {
path: __dirname + "/dist/assets",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader:'css-loader',
options: {
sourceMap: true,
url: false,
},
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './views/portal/index.html',
filename: '../index.html',
}),
new MiniCssExtractPlugin({
filename: './[name].css',
linkType: false,
ignoreOrder: false,
}),
new CopyPlugin({
patterns: [
{
from: './views/portal/**/*.*',
to: "../[name].[ext]",
globOptions: {
ignore: [
'**/index.*',
],
},
},
{
from: './assets/fonts/*.*',
to: "./[name].[ext]",
},
{
from: './assets/images/portal/*.*',
to: "./[name].[ext]",
},
{
from: './assets/theme/*.*',
to: "./[name].[ext]",
}
]
}),
],
Probably Webpack is not the right solution for me as I don;t want to change the source code as suggested in Webpack plugins and/or strategies for AngularJS
You can try something like this (we use it for running tests):
bundle.js:
const jsConext= require.context('.', true, /\.js/);
ng1Context.keys().forEach(ng1Context);
const cssConext= require.context('.', true, /\.css/);
ng1Context.keys().forEach(ng1Context);
...
entry: { 'app-portal': 'bundle.js' }
This should work in general (You might need fix order for css or in case of multiple angular modules etc.)

Webpack: Include vendor in only one module

I try to migrate one of my project building from browserify to webpack. It's AngularJS#1.7 project and I have multiple bundles: core with AngularJS and its dependenciy imports and other specific lazy-loading modules with its specific dependencies. So I have multiple entry points but I only load core.js script in index.html. Another modules resolve while routing by my resolver.
One of the lazy-loading modules imports AngularJS and it leads to duplicate code and WARNING: Tried to load AngularJS more than once. I want to Webpack check if modules were imported in core and load them from it.
I made it work but need to import one extra file common-vendors.js in index.html.
My config:
export default {
context: path.resolve(__dirname, './src/app'),
//...
entry: {
'core': './core/core.module',
'back-office': './back-office/back-office.module',
'front-office-1': './front-office-2/front-office-2.module',
'front-office-2': './front-office-3/front-office-3.module'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'www')
},
mode: config.env,
optimization: {
splitChunks: {
cacheGroups: {
'common-vendors': {
test: /[\\/]node_modules[\\/]/,
name: 'common-vendors',
chunks: 'initial',
minChunks: 2
}
}
},
runtimeChunk: {name: 'core'},
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {mangle: false}
})
]
},
plugins: [
new CompressionPlugin()
]
}
If you know a better solution, feel free to comment.

Why is the client trying to require a library when I am doing server side rendering?

I am trying to implement server side rendering on my react app, but having trouble with this one error.
this is my webpack.config.js file. When I run the script npm start everything compiles correctly.
var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
module.exports = {
entry: [
'./server.js'
],
target: 'node',
output: {
path: __dirname,
publicPath: '/',
filename: 'bundle.js'
},
externals: nodeModules,
module: {
loaders: [{
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015', 'stage-1']
}
}]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
devServer: {
historyApiFallback: true,
contentBase: './'
}
};
I believe the main issue is that webpack thinks bundle.js is build for node environment. Therefore it assumes that require is available. However it is meant for the browser, it will throw an error since require is not available. I'm not really sure if your webpack.config.js is for the server or the browser, since the entry point is the server.js and yet the output is for bundle.js, which I assume meant for the browser.
Have a look on server rendering tutorial here. Basically, you have to have a webpack config file for server and the browser. The server webpack config file would have an entry point where your server is defined. Whereas the browser webpack config file would have an entry point where react-dom render method is called. Then, make sure that the output of the browser webpack config is the one that is used in the html and not the server one.

Webpack - react works even when it's not in 'vendors'

I'm working with Webpack+React and I'm using the CommonsChunkPlugin. The thing is that react works even when I don't put it in the 'vendors' entry (same for other packages). Does that make sense?
My config looks like this:
config.entry.vendors = ['mobx', 'jquery', 'highcharts', 'react-highcharts', 'moment', 'numeral', 'jquery-ui', 'jquery.cookie', 'lodash', 'jquery.waitforimages', 'raven-js'];
config.module.loaders = config.module.loaders.concat([
{
test : /\.less$/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
loader : "css-loader?sourceMap!postcss-loader!less-loader?sourceMap"
})
}
]);
config.plugins = config.plugins.concat([
new ExtractTextPlugin('[name]-[chunkhash].min.css'),
new webpack.optimize.UglifyJsPlugin({
minimize : true,
mangle : false, // { except: ['$super', '$', 'exports', 'require'] },
compressor: {
warnings : false,
screw_ie8: true
},
sourceMap : true
}),
new StatsPlugin('webpack.stats.json', {
source : false,
modules: false
}),
new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors-[chunkhash].min.js'}),
new WebpackMd5Hash(),
new ManifestPlugin(),
new InlineManifestWebpackPlugin({
name: 'webpackManifest'
})
]);
The output value of webpack is:
output: {
filename : '[name].bundle.js',
publicPath : '/',
path : paths.dist,
sourceMapFilename: "[name].js.map",
},
CommonsChunkPlugin is smart enough, you didn't specify chunks property for CommonsChunkPlugin, that means that plugin will try to go through all your entries and move common parts to vendors chunk and then into vendors-[chunkhash].min.js file.
e.g. you have 2 entry points: index.js, signin.js and in both you have next code:
const React = require('react')
const ReactDOM = require('react-dom')
So with configuration like
entry: {
app: './index.js',
signin: './signin.js',
vendor: ['react']
},
*****
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor-[chunkhash].js'
})
]
you will still have react-dom in vendor chunk.
On another hand if you have third entry point without requiring react-dom, only react will be moved to vendor chunk.
But even in this case you will still have react-dom included into first two entry chunks.
The thing is that react works event when I don't put it in the 'vendors' entry (same for other packages).
So react will work in any case, the only difference will be, will react chunk be moved to vendors or not, if not, it still will be included in your entry point file.
Hope it helps.

Resources