Webpack 4 - create vendor chunk - node.js

In a webpack 3 configuration I would use the code below to create separate vendor.js chunk:
entry: {
client: ['./client.js'],
vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},
output: {
filename: '[name].[chunkhash].bundle.js',
path: '../dist',
chunkFilename: '[name].[chunkhash].bundle.js',
publicPath: '/',
},
plugins: [
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
}),
],
With all the changes I'm not sure how to do it with Webpack 4. I know that CommonChunksPlugin was removed, so there is a different way to achieve that. I've also read this tutorial but I'm still not sure about extracting runtime chunk and properly defining output property.
EDIT:
Unfortunately, I was experiencing issues with the most popular answer here. Check out my answer.

In order to reduce the vendor JS bundle size. We can split the node module packages into different bundle files. I referred this blog for splitting the bulky vendor file generated by Webpack. Gist of that link which I used initially:
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
}
If one wants to group multiple packages and chunk then into different bundles then refer following gist.
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "reactvendor"
},
utilityVendor: {
test: /[\\/]node_modules[\\/](lodash|moment|moment-timezone)[\\/]/,
name: "utilityVendor"
},
bootstrapVendor: {
test: /[\\/]node_modules[\\/](react-bootstrap)[\\/]/,
name: "bootstrapVendor"
},
vendor: {
test: /[\\/]node_modules[\\/](!react-bootstrap)(!lodash)(!moment)(!moment-timezone)[\\/]/,
name: "vendor"
},
},
},
}

In order to separate the vendors and the runtime you need to use the optimization option.
Possible Webpack 4 configuration:
// mode: 'development' | 'production' | 'none'
entry: {
client: ['./client.js'],
vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},
output: {
filename: '[name].[chunkhash].bundle.js',
path: '../dist',
chunkFilename: '[name].[chunkhash].bundle.js',
publicPath: '/',
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
enforce: true,
chunks: 'all'
}
}
}
}
More info related with W4 can be found in this Webpack-Demo.
Also, you can achieve the same changing the optimization.splitChunks.chunks property to "all". Read more here
Note: You can configure it via optimization.splitChunks. The examples say something about chunks, by default it only works for async chunks, but with optimization.splitChunks.chunks: "all" the same would be true for initial chunks.

There are a few examples located here:
https://github.com/webpack/webpack/tree/master/examples
Based on your example i believe this translate to:
// mode: "development || "production",
entry: {
client: './client.js',
},
output: {
path: path.join(__dirname, '../dist'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
publicPath: '/',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true
}

You could remove vendor out of the entry property and set the optimization property like so...
entry: {
client: './client.js'
},
output: {
path: path.join(__dirname, '../dist'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
publicPath: '/',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
chunks: 'initial',
name: 'vendor',
enforce: true
},
}
}
}
Check this source webpack examples

After some time I found out that this configuration:
entry: {
vendor: ['#babel/polyfill', 'react', 'react-dom', 'redux'],
client: './client.js',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true
}
was failing to somehow to load #babel/polyfill which was causing browser incompatibility errors... So recently I looked up to the updated webpack documentation and found a way to create explicit vendor chunk that was properly loading #babel/polyfill:
const moduleList = ["#babel/polyfill", "react", "react-dom"];
...
entry: {
client: ["#babel/polyfill", "../src/client.js"]
}
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: new RegExp(
`[\\/]node_modules[\\/](${moduleList.join("|")})[\\/]`
),
chunks: "initial",
name: "vendors",
enforce: true
}
}
}
}
Notice that I create one entry with all of the code included and then I specify with splitChunks.cacheGroups.vendor.test which modules should be split out to the vendor chunk.
Webpack documentation on SplitChunksPlugin.
Webpack guide on caching
Webpack author answer on the same problem
Still, I'm not sure if this is 100% correct or if it could be improved as this is literally one of the most confusing things ever. However, this seems to be closest to the documentation, seems to produce correct chunks when I inspect them with webpack-bundle-analyzer (only updates the chunks that were changed and rest of them stays the same across builds) and fixes the issue with polyfill.

I found a much shorter way to do this:
optimization: {
splitChunks: { name: 'vendor', chunks: 'all' }
}
When splitChunks.name is given as a string, the documentation says: "Specifying either a string or a function that always returns the same string will merge all common modules and vendors into a single chunk."
In combination with splitChunks.chunks, it will extract all dependencies.

I think if you do this:
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: true,
}
It will create a vendors~ and runtime~ chunk for you. Sokra said the default for splitChunks is this:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
Which already includes a vendors and default bundle. In testing, I haven't seen a default bundle appear.
I don't know what the expected workflow for including these files is, but I wrote this helper function in PHP:
public static function webpack_asset($chunkName, $extensions=null, $media=false) {
static $stats;
if($stats === null) {
$stats = WxJson::loadFile(WX::$path.'/webpack.stats.json');
}
$paths = WXU::array_get($stats,['assetsByChunkName',$chunkName],false);
if($paths === false) {
throw new \Exception("webpack asset not found: $chunkName");
}
foreach($stats['assetsByChunkName'] as $cn => $files) {
if(self::EndsWith($cn, '~' . $chunkName)) {
// prepend additional supporting chunks
$paths = array_merge($files, $paths);
}
}
$html = [];
foreach((array)$paths as $p) {
$ext = WXU::GetFileExt($p);
if($extensions) {
if(is_array($extensions)) {
if(!in_array($ext,$extensions)) {
continue;
}
} elseif(is_string($extensions)) {
if($ext !== $extensions) {
continue;
}
} else {
throw new \Exception("Unexpected type for \$extensions: ".WXU::get_type($extensions));
}
}
switch($ext) {
case 'js':
$html[] = WXU::html_tag('script',['src'=>$stats['publicPath'].$p,'charset'=>'utf-8'],'');
break;
case 'css':
$html[] = WXU::html_tag('link',['href'=>$stats['publicPath'].$p,'rel'=>'stylesheet','type'=>'text/css','media'=>$media],null); // "charset=utf-8" doesn't work in IE8
break;
}
}
return implode(PHP_EOL, $html);
}
Which works with my assets plugin (updated for WP4):
{
apply: function(compiler) {
//let compilerOpts = this._compiler.options;
compiler.plugin('done', function(stats, done) {
let assets = {};
stats.compilation.namedChunks.forEach((chunk, name) => {
assets[name] = chunk.files;
});
fs.writeFile('webpack.stats.json', JSON.stringify({
assetsByChunkName: assets,
publicPath: stats.compilation.outputOptions.publicPath
}), done);
});
}
},
All of this spits out something like:
<script src="/assets/runtime~main.a23dfea309e23d13bfcb.js" charset="utf-8"></script>
<link href="/assets/chunk.81da97be08338e4f2807.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.81da97be08338e4f2807.js" charset="utf-8"></script>
<link href="/assets/chunk.b0b8758057b023f28d41.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.b0b8758057b023f28d41.js" charset="utf-8"></script>
<link href="/assets/chunk.00ae08b2c535eb95bb2e.css" rel="stylesheet" type="text/css" media="print"/>
Now when I modify one of my custom JS files, only one of those JS chunks changes. Neither the runtime nor the vendors bundle needs to be updated.
If I add a new JS file and require it, the runtime still isn't updated. I think because the new file will just be compiled into the main bundle -- it doesn't need to be in the mapping because it's not dynamically imported. If I import() it, which causes code-splitting, then the runtime gets updated. The vendors bundle also appears to have changed -- I'm not sure why. I thought that was supposed to be avoided.
I also haven't figured out how to do per-file hashes. If you modify a .js file which is the same chunk as a .css file, both their filenames will change with [chunkhash].
I updated the assets plugin above. I think the order in which you include the <script> tags might matter... this will maintain that order AFAICT:
const fs = require('fs');
class EntryChunksPlugin {
constructor(options) {
this.filename = options.filename;
}
apply(compiler) {
compiler.plugin('done', (stats, done) => {
let assets = {};
// do we need to use the chunkGraph instead to determine order??? https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693#gistcomment-2381967
for(let chunkGroup of stats.compilation.chunkGroups) {
if(chunkGroup.name) {
let files = [];
for(let chunk of chunkGroup.chunks) {
files.push(...chunk.files);
}
assets[chunkGroup.name] = files;
}
}
fs.writeFile(this.filename, JSON.stringify({
assetsByChunkName: assets,
publicPath: stats.compilation.outputOptions.publicPath
}), done);
});
}
}
module.exports = EntryChunksPlugin;

It seems the order of entry files also matter. Since you have client.js before vendor, the bundling doesn't happen of vendor before your main app.
entry: {
vendor: ['react', 'react-dom', 'react-router'],
app: paths.appIndexJs
},
Now with the SplitChunks optimisation you can specify the output file name and refer to the entry name vendor as:
optimization: {
splitChunks: {
cacheGroups: {
// match the entry point and spit out the file named here
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
filename: 'vendor.js',
enforce: true,
},
},
},
},

Related

can i modify vendors.js file name with webpack?

I have to modify filenames when build vuejs project with webpack.
I'm using webpack 4.x.x vuejs 2.x.x version.
And have to modify file name of
"vendors.js" and "runtime.js" to
"vendors.pc.js" and "runtime.pc.js"
Can I change them when build it?
add this to your webpack.config.js, then webpack will map node_modules files into different chunk files according to your cacheGroups settings
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "reactvendor"
},
utilityVendor: {
test: /[\\/]node_modules[\\/](lodash|moment|moment-timezone)[\\/]/,
name: "utilityVendor"
},
bootstrapVendor: {
test: /[\\/]node_modules[\\/](react-bootstrap)[\\/]/,
name: "bootstrapVendor"
},
vendor: {
test: /[\\/]node_modules[\\/](!react-bootstrap)(!lodash)(!moment)(!moment-timezone)[\\/]/,
name: "vendor"
},
},
},
}

How to reduce the number chunks using Vite?

Vite produces unreasonably small chunks (90% of all chunks are under 1KB with a few 300KB+).
Here is the full build log:
https://gist.github.com/gajus/0149233592085181331dde7076fc50b1
This is example website:
https://contra.com/p/gkOQlbLq-validating-postgre-sql-results-and-inferring-query-static-types
This is out Vite configuration:
import path from 'path';
import { default as react } from '#vitejs/plugin-react';
import { defineConfig } from 'vite';
import { default as ssr } from 'vite-plugin-ssr/plugin';
const { VITE_BASE_URL } = process.env;
export default defineConfig(({ ssrBuild }) => {
let build;
if (!ssrBuild) {
build = {
emptyOutDir: true,
manifest: true,
minify: true,
polyfillModulePreload: false,
rollupOptions: {
output: {
chunkFileNames: 'chunk-[name].[hash].js',
entryFileNames: 'entry-[name].[hash].js',
inlineDynamicImports: false,
sourcemap: true,
},
},
sourcemap: true,
};
}
return {
base: VITE_BASE_URL ?? '/static/',
build,
plugins: [
react({
babel: {
plugins: [
'babel-plugin-relay',
[
'babel-plugin-styled-components',
{
displayName: true,
fileName: false,
pure: true,
ssr: true,
},
],
],
},
}),
ssr(),
],
resolve: {
alias: {
'#': path.resolve(__dirname, './src/'),
},
},
};
});
If you try to inspect this page using Google tools, it doesn't even load – presumably because of 200 JavaScript chunks that that page needs to load.
https://search.google.com/test/mobile-friendly/result?id=ULZhtOfQfp1Gxj2wYhyCUw
How to reduce the number of chunks?

how to use `dependOn` to bundle separate application files from the main entry point?

I have a backend project that I would like to bundle using Webpack v5.
I would like to split some of the files into their own bundles and have them imported into the main entry.
The main entry has a dependOn key that contains an array of filenames that will be used by the main entry.
This is the webpack.config.json:
module.exports = {
entry: {
app: {
import: './index.ts',
dependOn: 'routes'
},
routes: ['./routes.ts']
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.resolve(__dirname, 'build')
},
resolve: {
extensions: ['.ts', '.js']
},
target: 'node',
externals: [
nodeExternals()
],
mode: 'production',
optimization: {
minimize: false,
},
module: {
rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]
},
plugins: [
new ContextReplacementPlugin(/any-promise/)
]
};
After running the build two files are generated (app.js & routes.js) as expected. But when running the entry point with NodeJS I get the following error: webpack_require.C does not exist.
Going through the documentation I see that their example dependOn is based on packages and not indvidual application files.
Can you add applicaton files to dependOn? Does this have something to do with module resolution?
Let me assume you have imported the routes in your app, and you want to split routes into a separate bundle instead of being included in the bundled app.js.
Here's how you could make it with SplitChunksPlugin https://webpack.js.org/plugins/split-chunks-plugin.
module.exports = {
entry: {
app: "./index"
},
target: "node",
optimization: {
splitChunks: {
cacheGroups: {
routes: {
filename: "routes.js",
test: module => {
return module.resource && module.resource.includes("routes");
},
enforce: true,
chunks: "all"
}
}
}
}
};
Webpack will output two files, app.js and routes.js.

JHipster - How to compile SCSS files directly with angular 7

Normaly it is really easy to add SCSS styles to components with angular components (just create the scss file and import it in the component.ts), however this styles are not rendered to normal css files, the styles are embeded (from what i understand, i am very new with angular).
This creates a problem, i am trying to use a theme with some dinamic skins with a "customization panel", but this component needs the path to my compiled css indepedently.
To achieve this, in the vendor basic app, i can see they added this to the angular.js:
{
...
"projects": {
"angular-starter": {
...,
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/angular-starter",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
{ "input": "src/vendor/styles/appwork.scss", "bundleName": "vendor/styles/appwork", "lazy": true },
{ "input": "src/vendor/styles/appwork-material.scss", "bundleName": "vendor/styles/appwork-material", "lazy": true },
{ "input": "src/vendor/styles/bootstrap.scss", "bundleName": "vendor/styles/bootstrap", "lazy": true },
{ "input": "src/vendor/styles/bootstrap-material.scss", "bundleName": "vendor/styles/bootstrap-material", "lazy": true },
// More styles like this
],
...
And then it can be referenced directly as css file, as the index shows:
<html lang="en" class="default-style">
<head>
...
<!-- Here it references the compiled scss as css directly -->
<link rel="stylesheet" href="vendor/styles/bootstrap.css" class="theme-settings-bootstrap-css">
<link rel="stylesheet" href="vendor/styles/appwork.css" class="theme-settings-appwork-css">
<link rel="stylesheet" href="vendor/styles/theme-corporate.css" class="theme-settings-theme-css">
<link rel="stylesheet" href="vendor/styles/colors.css" class="theme-settings-colors-css">
<link rel="stylesheet" href="vendor/styles/uikit.css">
...
<script>
// Here uses the path of the compiled css as parameter,
// this way the skin selector changes the css used in the page
window.themeSettings = new ThemeSettings({
cssPath: 'vendor/styles/',
themesPath: 'vendor/styles/'
});
</script>
</head>
...
</html>
However
Cheking the angular.js generated from jhipster, i can see the architect part is empty:
{
"$schema": "./node_modules/#angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"consuldent": {
"root": "",
"sourceRoot": "src/main/webapp",
"projectType": "application",
"architect": {}
}
},
I do not know if this is because in the example code it uses ng serve to run the demo page, using a node.js server, where jhipster uses spring directly, i tried adding the styles part to the jhipster's angular.js file, but i can not find any route where the css could be loaded, so im guessing it is just ignoring the code that i added
In angular, the view is encapsulated with each unique attribute.
add encapsulation: ViewEncapsulation.None in main component, hence the css will be shared by all components
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
name = 'Angular app';
}
Jhipster uses webpack to pack the angular modules. You want to bundle the css hence
copy your css into /src/main/webapp/vendor/styles/appwork.css'
add an entry in webpack.common.js
new CopyWebpackPlugin([
{ from: './node_modules/swagger-ui/dist/css', to: 'swagger-ui/dist/css' },
{ from: './node_modules/swagger-ui/dist/lib', to: 'swagger-ui/dist/lib' },
{ from: './node_modules/swagger-ui/dist/swagger-ui.min.js', to: 'swagger-ui/dist/swagger-ui.min.js' },
{ from: './src/main/webapp/swagger-ui/', to: 'swagger-ui' },
{ from: './src/main/webapp/content/', to: 'content' },
{ from: './src/main/webapp/vendor/styles/appwork.css', to: 'content/vendor/styles/appwork.css' },
{ from: './src/main/webapp/favicon.ico', to: 'favicon.ico' },
{ from: './src/main/webapp/manifest.webapp', to: 'manifest.webapp' },
// jhipster-needle-add-assets-to-webpack - JHipster will add/remove third-party resources in this array
{ from: './src/main/webapp/robots.txt', to: 'robots.txt' }
]),
To compile custom sass
Add the entry in webpack.prod.js , for example myfile and myfile2. Then change the sass compiler to include our myfile.scss, myfile2.scss, finally comment the MiniCssExtractPlugin
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
.
.
.
.
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
.
.
.
.
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
.
.
.
.
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
.
.
.
.
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
.
.
.
.
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// filename: '[name].[contenthash].css',
// chunkFilename: '[id].css'
}),
Your webpack.prod.js file look like this
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const Visualizer = require('webpack-visualizer-plugin');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const AngularCompilerPlugin = require('#ngtools/webpack').AngularCompilerPlugin;
const path = require('path');
const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');
const ENV = 'production';
const sass = require('sass');
module.exports = webpackMerge(commonConfig({ env: ENV }), {
// Enable source maps. Please note that this will slow down the build.
// You have to enable it in UglifyJSPlugin config below and in tsconfig-aot.json as well
// devtool: 'source-map',
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
output: {
path: utils.root('build/www'),
filename: 'app/[name].[hash].bundle.js',
chunkFilename: 'app/[id].[hash].chunk.js'
},
module: {
rules: [{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '#ngtools/webpack'
},
{
test: /\.scss$/,
use: ['to-string-loader', 'css-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}],
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
},
{
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: { implementation: sass }
}
]
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
},
{
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
runtimeChunk: false,
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
minimizer: [
new TerserPlugin({
parallel: true,
cache: true,
terserOptions: {
ie8: false,
// sourceMap: true, // Enable source maps. Please note that this will slow down the build
compress: {
dead_code: true,
warnings: false,
properties: true,
drop_debugger: true,
conditionals: true,
booleans: true,
loops: true,
unused: true,
toplevel: true,
if_return: true,
inline: true,
join_vars: true
},
output: {
comments: false,
beautify: false,
indent_level: 2
}
}
}),
new OptimizeCSSAssetsPlugin({})
]
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// filename: '[name].[contenthash].css',
// chunkFilename: '[id].css'
}),
new MomentLocalesPlugin({
localesToKeep: [
'en',
'es'
// jhipster-needle-i18n-language-moment-webpack - JHipster will add/remove languages in this array
]
}),
new Visualizer({
// Webpack statistics in target folder
filename: '../stats.html'
}),
new AngularCompilerPlugin({
mainPath: utils.root('src/main/webapp/app/app.main.ts'),
tsConfigPath: utils.root('tsconfig-aot.json'),
sourceMap: true
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
})
],
mode: 'production'
});
similarly do the same step for webpack.dev.js
const webpack = require('webpack');
const writeFilePlugin = require('write-file-webpack-plugin');
const webpackMerge = require('webpack-merge');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
const path = require('path');
const sass = require('sass');
const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');
const ENV = 'development';
module.exports = (options) => webpackMerge(commonConfig({ env: ENV }), {
devtool: 'eval-source-map',
devServer: {
contentBase: './build/www',
proxy: [{
context: [
/* jhipster-needle-add-entity-to-webpack - JHipster will add entity api paths here */
'/api',
'/management',
'/swagger-resources',
'/v2/api-docs',
'/h2-console',
'/auth'
],
target: `http${options.tls ? 's' : ''}://127.0.0.1:8080`,
secure: false,
changeOrigin: options.tls,
headers: { host: 'localhost:9000' }
}],
stats: options.stats,
watchOptions: {
ignored: /node_modules/
}
},
entry: {
polyfills: './src/main/webapp/app/polyfills',
global: './src/main/webapp/content/scss/global.scss',
myfile: './src/main/webapp/content/scss/myfile.scss',
myfile2: './src/main/webapp/content/scss/myfile2.scss',
main: './src/main/webapp/app/app.main'
},
output: {
path: utils.root('build/www'),
filename: 'app/[name].bundle.js',
chunkFilename: 'app/[id].chunk.js'
},
module: {
rules: [{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
exclude: [/(node_modules)/, new RegExp('reflect-metadata\\' + path.sep + 'Reflect\\.ts')]
},
{
test: /\.ts$/,
use: [
'angular2-template-loader',
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('build/cache-loader')
}
},
{
loader: 'thread-loader',
options: {
// there should be 1 cpu for the fork-ts-checker-webpack-plugin
workers: require('os').cpus().length - 1
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
},
'angular-router-loader'
],
exclude: /(node_modules)/
},
{
test: /\.scss$/,
use: ['to-string-loader', 'css-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}],
exclude: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/
},
{
test: /(vendor\.scss|global\.scss|myfile\.scss|myfile2\.scss)/,
use: ['style-loader', 'css-loader', 'postcss-loader', {
loader: 'sass-loader',
options: { implementation: sass }
}]
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
exclude: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/
},
{
test: /(vendor\.css|global\.css|myfile\.css|myfile2\.css)/,
use: ['style-loader', 'css-loader']
}]
},
stats: process.env.JHI_DISABLE_WEBPACK_LOGS ? 'none' : options.stats,
plugins: [
process.env.JHI_DISABLE_WEBPACK_LOGS
? null
: new SimpleProgressWebpackPlugin({
format: options.stats === 'minimal' ? 'compact' : 'expanded'
}),
new FriendlyErrorsWebpackPlugin(),
new ForkTsCheckerWebpackPlugin(),
new BrowserSyncPlugin({
host: 'localhost',
port: 9000,
proxy: {
target: 'http://localhost:9060'
},
socket: {
clients: {
heartbeatTimeout: 60000
}
}
}, {
reload: false
}),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)/,
path.resolve(__dirname, './src/main/webapp')
),
new writeFilePlugin(),
new webpack.WatchIgnorePlugin([
utils.root('src/test'),
]),
new WebpackNotifierPlugin({
title: 'JHipster',
contentImage: path.join(__dirname, 'logo-jhipster.png')
})
].filter(Boolean),
mode: 'development'
});
Then if you want the css to be include in your index.html then change the webpack.common.js
new HtmlWebpackPlugin({
template: './src/main/webapp/index.html',
chunks: ['vendors', 'polyfills', 'main', 'global','myfile','myfile2'],
chunksSortMode: 'manual',
inject: 'body'
})
Add your custom styles to src/main/webapp/content/scss/global.scss

Extract local and global styles with mini-css-extract-plugin

Until now I have been using extract-text-webpack-plugin and webpack 3 to make two bundle files. One for local styles and one for gloabl styles.
So in global styles file we would extract css from like bootstrap, semantic ... and in local styles bundle we would put our own styles.
Both of those files would have contenthash so if for example I change something in my local styles and rebuild app, only the hash from local styles would change and not from globaly styles.
After updateing to webpack 4 a need to use mini-css-extract-plugin instead of extract-text-webpack-plugin.
This was my setup until now. I am trying different things but I dont know how to turn this setup for mini-css-extract-plugin?
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const ExtractLocal = new ExtractTextPlugin({
filename: 'stylesheet/stylesLocal.[contenthash].local.css',
disable: false,
allChunks: true,
})
const ExtractGlobal = new ExtractTextPlugin({
filename: 'stylesheet/stylesGlobal.[contenthash].css',
disable: false,
allChunks: true,
})
module.exports = {
module: {
rules: [
/* Local styles */
{
test: /\.local\.(css|scss)$/,
use: ExtractLocal.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
minimize: true,
modules: true,
importLoaders: 1,
localIdentName: '[local]___[name]__[hash:base64:5]',
},
},
...
],
}),
},
/* Global styles */
{
test: /^((?!\.local).)+\.(css)$/,
use: ExtractGlobal.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
minimize: true,
},
},
],
}),
},
],
},
plugins: [
ExtractLocal,
ExtractGlobal,
...
],
}
Your css loaders are correct.
In plugins, I see you want implement it using mini-css to extract multiple css files.
While it's definitely an options, I succeeded implementing it with webpack Optimization option, and only 1 mini-css in plugins.
Output config:
output: {
path: appConfig.paths.appBuild,
filename: 'scripts/[name].[chunkhash:8].js',
chunkFilename: 'scripts/[name].[chunkhash:8].chunk.js',
publicPath: publicPath,
},
Additional css rule for styles from node modules only (I made it the first css rule and after it the additional rules):
{
test: /\.(css|scss)$/,
include: /(node_modules)/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'sass-loader', options: { sourceMap: true } },
],
},
Optimization config:
(This will extract vendor js as well.)
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
Plugin config:
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css',
chunkFilename: 'styles/[name].[contenthash].css',
}),

Resources