Webpack: vendor bundle not imported in main output - javascript

I'm struggling with what looks like a generic error from Webpack after trying to optimise my source code.
Assuming I have the following files in ./src:
├── main.js
├── moduleA.js
└── moduleB.js
main.js imports and uses ModuleA.
moduleA.js imports and uses ModuleB
ModuleA.js and ModuleB.js both import flatten-array from node_modules
My expectation is that if I try to optimise my bundle (see below) it will output two files:
1. index.js
2. vendors~main.index.js
Trying to execute the index.js output bundle results in:
/******/ modules[moduleId].call(module.exports, module,
module.exports, __webpack_require__);
^
TypeError: Cannot read property 'call' of undefined
Although the files are generated, index.js doesn't appear to import vendors~main.index.js. Yet it executes fine when removing the optimization (and vendors javascript)
Is this the correct assumption? How can I make it work like this?
While this is a bundle for Node, there are valid reasons that I'd like to export a vendors file.
Accompanying git repo to reproduce available here:
https://github.com/supasympa/webpack-vendors-issue
Files are:
main.js
const moduleA = require('./moduleA');
moduleA.log('log from main.js');
moduleA.js
const moduleB = require('./moduleB');
const flatten = require('array-flatten');
module.exports.log = function(msg){
moduleB.log('logging from moduleA.js');
console.log(`ModuleA logging: ${msg}`);
console.log(`flattened: ${flatten([[1,2,3],[4,5],[6,7]])}`)
};
moduleB.js
const flatten = require('array-flatten');
module.exports.log = function(msg){
console.log(`ModuleB logging: ${msg}`);
console.log(`flattened: ${flatten([[1,2,3],[4,5],[6,7]])}`)
};
webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
module: {
rules: [{
include: [path.resolve(__dirname, 'src')],
loader: 'babel-loader',
options: {
plugins: ['syntax-dynamic-import'],
presets: [['env', {
'modules': 'commonjs'
}]]
},
test: /\.js$/
}]
},
entry: './src/main',
target: 'node',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
priority: -10,
test: /[\\/]node_modules[\\/]/,
enforce: true
},
},
// concatenateModules: false,
chunks: 'all',
minChunks: 1,
minSize: 0,
name: true
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
]
};

It turns out that this is not yet implemented outside of the browser, in Webpack.
https://github.com/webpack/webpack/issues/8330
https://github.com/webpack/webpack/issues/8161
https://github.com/webpack/webpack/issues/8156

Defining, chunks: 'all', you're explicitly taking all initial entries and async imports (by default it only on-demand async chunks) and specifying the bundler to create a new vendor chunk/file out of that.
So the behavior is as expected. The idea is to remove the common dependencies from entry files so they can be shared and the page has to load less vendors/common code overall.
One way to explicitly control what the entry files include is this pattern: https://github.com/webpack/webpack/tree/master/examples/two-explicit-vendor-chunks

Related

How to exclude files from a webpack entry point

I am using the following webpack config to handle / load all of my sass files. One important item is that I am using additionalData to preappend my common styles. This is causing a conflict, however, because my entry for webpack is any and all sass files, which means that my commonStyles.scss is already getting loaded. When I run my app, I get the following error:
errorMessage:
SassError: This file is already being loaded.
╷
1 │ #import "src/styles/commonStyles.scss";
│ ^^^^^^^^^^^^^^^^^^^^^^^
╵
src/styles/sizes.scss 1:9 root stylesheet
How can I create an entry point that includes all sass files in my app except for / excluding commonStyles.scss (or better yet, any files in the /src/styles/ folder)? Thanks for the help!
webpack config:
'use strict';
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const sassRegex = /\.(scss|sass)$/;
module.exports = {
mode: 'production',
bail: true,
devtool: false,
entry: glob.sync(`${path.resolve(fs.realpathSync(process.cwd()), 'src')}/**/*.{css,scss}`),
output: {
path: 'build',
},
module: {
rules: [
{
test: sassRegex,
exclude: /node_modules/,
use: [
{
loader: 'sass-loader',
options: {
sourceMap: true,
additionalData: `#import "src/styles/commonStyles.scss";`,
},
},
],
},
],
},
};
Instead of
/**/*.{css,scss} try /**/!{commonStyles}.{css,scss}
Alternatively:
entry: glob.sync(
`${path.resolve(fs.realpathSync(process.cwd()), 'src')}/**/*.{css,scss}`,
ignore: [
'**/src/styles/commonStyles.scss',
],
),

How do I prevent webpack from throwing typescript errors for unused modules?

I have the following structure:
└── src
├── tsconfig.json
├── core
│ ├── [...].ts
└── ui
├── [...].tsx
└── tsconfig.json
In the frontend I import a small number of modules from the core. These modules, and any dependent modules, are compliant with both tsconfig files.
tsc and eslint pass with no errors and webpack builds the desired output file. So far so good.
However when webpack builds it throws loads of type errors for other backend modules.
How do I suppress these errors? I tried excluding src/core from babel-loader and including the required modules but I was still getting the same errors.
/// webpack.config.js
/* eslint-disable #typescript-eslint/no-var-requires */
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/ui',
output: {
path: path.resolve(__dirname, './ui-dist'),
},
// Enable sourcemaps for debugging webpack's output.
devtool: 'source-map',
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
babelrc: false,
presets: [
[
'#babel/preset-env',
{ targets: { browsers: 'last 2 versions' } }, // or whatever your project requires
],
'#babel/preset-typescript',
'#babel/preset-react',
],
plugins: [
// plugin-proposal-decorators is only needed if you're using experimental decorators
// in TypeScript
['#babel/plugin-proposal-decorators', { legacy: true }],
['#babel/plugin-transform-runtime', { legacy: true }],
['#babel/plugin-proposal-class-properties', { loose: true }],
'react-hot-loader/babel',
],
},
},
},
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
},
],
},
plugins: [
new ForkTsCheckerWebpackPlugin({
tsconfig: path.resolve(__dirname, './src/ui/tsconfig.json'),
eslint: true,
/** Options to supply to eslint https://eslint.org/docs/developer-guide/nodejs-api#cliengine */
// eslintOptions: EslintOptions;
}),
new HtmlWebpackPlugin({
title: 'development',
template: './src/ui/template.html',
}),
],
// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
// dependencies, which allows browsers to cache those libraries between builds.
// externals: {
// react: 'React',
// 'react-dom': 'ReactDOM',
// },
devServer: {
contentBase: path.resolve(__dirname, './ui-dist'),
},
};
EDIT: I suppose I am referencing these modules throwing an error by using import type { x } from '../core/index.ts'. Perhaps I need to find a way for babel-loader to skip scanning type imports.
removing ForkTsCheckerWebpackPlugin did the trick. Type checking is done when calling tsc in any case.

In Webpack v4 with multiple entries, my splitChunks is not optimized, if my two pages includes core-js, they will all get a copy

You can clone my minimum repo https://github.com/rockmandash/webpack-chunks-question
Or see the code below
page1.js
import 'core-js';
console.log('I am page1');
page2.js
import 'core-js';
import 'react';
import 'react-dom';
console.log('I am page2');
My webpack config:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const entriesFilePath = [
require.resolve('./src/page1.js'),
require.resolve('./src/page2.js'),
];
const mode = 'development';
const webpackConfig = entriesFilePath.map((entrieFilePath) => {
const fileName = path.basename(entrieFilePath, path.extname(entrieFilePath));
// fileName would be page1 and page2
return {
mode,
devtool: 'cheap-module-source-map',
entry: {
[fileName]: entrieFilePath,
},
output: {
filename: 'static/js/[name].js',
chunkFilename: 'static/js/[name].chunk.js',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
filename: `${fileName}.html`,
},
),
),
].filter(Boolean),
};
});
module.exports = webpackConfig; // I have to export an array, because in the future, I need to do something else.
The generated dist folder looks like this:
dist
/page1.html
/page2.html
/static
/js
/page1.js.map
/vendors~page2.chunk.js.map
/vendors~page1.chunk.js // this includes core-js !!!!
/page1.js
/page2.js
/vendors~page1.chunk.js.map
/vendors~page2.chunk.js // this includes core-js too !!!!
/page2.js.map
You see, the generated two chunks both includes core-js, how can I make my webpack config smart enough to automatically separate core-js or other common vendor files out of the box?
You do not need to import core-js anywhere. Create a .babelrc file.
{
"presets": [
[
"#babel/preset-env",
{
"debug": true,
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
Instal #babel/core, #babel/polyfill, #babel/preset-env, babel-loader
Add to webpack
const optimization = {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'vendor',
chunks: 'all',
}
}
}
};
module: {rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]},
Take a look at my solution, there is a complete code. Use of core-js. He uses only part polyfil when it is needed.
https://github.com/tomik23/webpack-babel-corejs/blob/master/webpack.config.js#L17
And the second solution needed in your code is to use spliChunks
https://github.com/tomik23/photoBlog/blob/master/config/webpack.config.js#L31
P.S. If you import 'core-js' in this way; you download the whole core-js if you use my method then core-js chooses only what is needed and packages are smaller.

Optimal webpack.config with webpack 2 for AngularJS 1.x app?

I want to set up an Angular 1.x app from scratch using webpack 2.
I am having trouble finding the best configuration for webpack.config, with optimal entry and output for production (meaning, all code, style and templating minified and gziped with no code repetition).
My main problem is how to set up webpack.config so that it recognizes all partials within the folder structure of my project, like these:
My current config file, for reference (which can't see subfolders):
var HtmlWebpackPlugin = require( 'html-webpack-plugin' );
var ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
var path = require( 'path' );
module.exports = {
devServer: {
compress: true,
contentBase: path.join( __dirname, '/dist' ),
open: true,
port: 9000,
stats: 'errors-only'
},
entry: './src/app.js',
output: {
path: path.join( __dirname, '/dist' ),
filename: 'app.bundle.js'
},
module: {
rules: [ {
test: /\.scss$/,
use: ExtractTextPlugin.extract( {
fallback: 'style-loader',
use: [
'css-loader',
'sass-loader'
],
publicPath: '/dist'
} )
} ]
},
plugins: [
new HtmlWebpackPlugin( {
hash: true,
minify: { collapseWhitespace: true },
template: './src/index.html',
title: 'Prov'
} ),
new ExtractTextPlugin( {
filename: 'main.css',
allChunks: true
} )
]
};
Note that this isn't an exhaustive solution, as there are many optimizations one can make in the frontend, and I've kept the code snippets fairly short.
With webpack, there are a few routes that you can take to include partials into your app.js.
Solution 1
You can import/require your partials within app.js as such:
app.js
var angular = require('angular');
var proverbList = require('./proverb/list/proverb.list');
// require other components
// set up your app as normal
This allows the app.bundle.js to include your component js files in the main bundle. You can also use html-loader to include templates in the final bundle.
This isn't ideal, as all it does is create a large bundle.js (which doesn't leverage multiple downloads with http2 nor does it allow loading of components/files when the user explicitly requires it).
Solution 2
Importing partials as separate entry files into your webpack bundle:
webpack.config.js
const globby = require('globby');
const sourceDir = 'src';
var webpackentry = {
app: `${__dirname}/src/app.js`
};
const glob = globby.sync(`${__dirname}/${sourceDir}/**/*.js`)
.map((file)=>{
let name = file.split('/').pop().replace('.js', '');
webpackentry[name] = file;
});
const config = {
entry: webpackentry,
...
}
The second solution is unorthodox but it can be useful if you wanted to split all your partials as <script> tags in your html (for example if your company/team uses that as a means to include your directive/components/controllers), or if you have an app-2.bundle.js.
Solution 3
Use CommonsChunkPlugin:
webpack.config.js
let webpackentry = {
vendor: [
'module1',
'module2',
'module3',
]
}
...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'] //... add other modules
})
]
CommonsChunkPlugin allows webpack to scrawl through your entry files and discern common modules that are shared among them. This means that even if you are importing module1 in different files, they will be compiled only once in your final bundle.

Different main entry point in package.json for node and browser

In isomorphic react app I have myModule which should behave differently on node and browser environments. I would like configure this split point in package.json for myModule:
package.json
{
"private": true,
"name": "myModule",
"main": "./myModule.server.js",
"browser": "./myModule.client.js"
}
file structure
├── myModule
│   ├── myModule.client.js
│   ├── myModule.server.js
│ └── package.json
│
├── browser.js
└── server.js
So when I use myModule in node I should get only myModule.server.js:
server.js
import myModule from './myModule';
myModule(); // invoke myModule.server.js
On the browser side should build bundle only with myModule.client.js:
browser.js
import myModule from './myModule';
myModule(); // invoke myModule.client.js
react-starter-kit uses this approach but I can't figure out where is this configuration defined.
Motivation
package.json is good semantic point to do this kind of splitting.
Client side bundle only contain myModule.client.js.
Known solution - not an answer for me
You can have this kind of file structure:
├── myModule
│    ├── myModule.client.js
│    ├── myModule.server.js
│    └── index.js <-- difference
│
├── browser.js
└── server.js
And in index.js:
if (process.browser) { // this condition can be different but you get the point
module.exports = require('./myModule.client');
} else {
module.exports = require('./myModule.server');
}
The main problem with this is that client bundle contains a lot of heavy kB backend code.
My webpack configuration
I include my webpack.config.js. Strangely this config always point to myModule.client.js for browser and node.
const webpack = require('webpack');
var path = require('path');
var fs = require('fs');
const DEBUG = !process.argv.includes('--release');
const VERBOSE = !process.argv.includes('--verbose');
const AUTOPREFIXER_BROWSERS = [
'Android 2.3',
'Android >= 4',
'Chrome >= 35',
'Firefox >= 31',
'Explorer >= 9',
'iOS >= 7',
'Opera >= 12',
'Safari >= 7.1',
];
let nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1 ;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
let loaders = [
{
exclude: /node_modules/,
loader: 'babel'
},
{
test: [/\.scss$/,/\.css$/],
loaders: [
'isomorphic-style-loader',
`css-loader?${DEBUG ? 'sourceMap&' : 'minimize&'}modules&localIdentName=` +
`${DEBUG ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]'}`,
'postcss-loader?parser=postcss-scss'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
loader: 'url-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
limit: 10000,
},
},
{
test: /\.(eot|ttf|wav|mp3)$/,
loader: 'file-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
},
},
{
test: /\.json$/,
loader: 'json-loader',
},
];
const common = {
module: {
loaders
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
],
postcss: function plugins(bundler) {
var plugins = [
require('postcss-import')({ addDependencyTo: bundler }),
require('precss')(),
require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
];
return plugins;
},
resolve: {
root: path.resolve(__dirname, 'src'),
extensions: ['', '.js', '.jsx', '.json']
}
};
module.exports = [
Object.assign({} , common, { // client
entry: [
'babel-polyfill',
'./src/client.js'
],
output: {
path: __dirname + '/public/',
filename: 'bundle.js'
},
target: 'web',
node: {
fs: 'empty',
},
devtool: DEBUG ? 'cheap-module-eval-source-map' : false,
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': true }),
],
}),
Object.assign({} , common, { // server
entry: [
'babel-polyfill',
'./src/server.js'
],
output: {
path: __dirname + '',
filename: 'server.js'
},
target: 'node',
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': false }),
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
externals: nodeModules,
})
];
The behavior is standardized here: https://github.com/defunctzombie/package-browser-field-spec
Although this specification is unofficial, many Javascript bundlers follow it, including Webpack, Browserify, and the React Native packager. The browser field not only allows you to change your module entry point, but to also replace or ignore individual files within your module. It's quite powerful.
Since Webpack bundles code for the web by default, you need to manually disable the browser field if you want to use Webpack for your server build. You can do that using the target config option to do this: https://webpack.js.org/concepts/targets/
It has been a long time since this question was asked. I just want to clarify the previous answer.
If you look at tools/webpack.config.js in React Starter Kit you will
see that it exports two Webpack configurations that slightly differ,
e.g. module.exports = [clientConfig, sererConfig]. The server-side
bundle config has this field target set to node (by default it's web).
It seems this webpack beheavior is not documented, but webpack automatically takes 'main' entry when target is 'node' and takes 'browser' entry when target is 'web'.
If you look at tools/webpack.config.js in React Starter Kit you will see that it exports two Webpack configurations that slightly differ, e.g. module.exports = [clientConfig, sererConfig]. The server-side bundle config has this field target set to node (by default it's web).
https://webpack.github.io/docs/configuration.html#target
The approach that you described works great for modules that have exactly the same API but different implementations, like in the case with HTTP client utility that uses XMLHttpRequest in its browser-specific implementation and Node's http module in its server implementation:
https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch
To have a different entry point for client and server in a Node Module, you can use process.browser flag and handle the same
if (process.browser) {
// load client entry point
} else {
// load server entry point
}

Categories

Resources