What I want is to bundle my JavaScript vendor files in a specific order via CommonsChunkPlugin from Webpack.
I'm using the CommonsChunkPlugin for Webpack. The usage from the official documentation is straight forward and easy. It works as intended but I believe the plugin is bundling my files in alphabetical order (could be wrong). There are no options for the plugin to specify the order they should be bundled.
Note: For those who are not familiar with Bootstrap 4, it currently
requires a JavaScript library dependency called Tether.
Tether must be loaded before Bootstrap.
webpack.config.js
module.exports = {
entry: {
app: './app.jsx',
vendor: ['jquery', 'tether', 'bootstrap', 'wowjs'],
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
],
};
Two things are happening here:
vendor.bundle.js contains bootstrap, jquery, tether,
wowjs
bundle.js contains the rest of my application
Bundling order:
correct: jquery, tether, bootstrap, wowjs
incorrect: bootstrap, jquery, tether, wowjs
Notice in my webpack.config.js I ordered them exactly as they should but they are bundled in the incorrect order. It doesn't matter if I rearrange them randomly the result is the same.
After I use Webpack to build my application, the vendor.bundle.js shows me the incorrect order.
I know they're bundled incorrectly cause Chrome Dev. Tools tell me there are dependency issues. When I view the file through the tool and my IDE, it is bundled in the incorrect order.
My other approach also resulted in the same issue
I also tried import and require in my entry file (in this case, app.jsx) without the use of the CommonChunkPlugin and that also loads my JavaScript libraries in alphabetical order for some reason.
webpack.config.js
module.exports = {
entry: './app.jsx',
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
app.jsx (entry)
import './node_modules/jquery/dist/jquery.min';
import './node_modules/tether/dist/js/tether.min';
import './node_modules/bootstrap/dist/js/bootstrap.min';
import './node_modules/wowjs/dist/wow.min';
or
require('./node_modules/jquery/dist/jquery.min');
require('./node_modules/tether/dist/js/tether.min');
require('./node_modules/bootstrap/dist/js/bootstrap.min');
require('./node_modules/wowjs/dist/wow.min');
The result?
Bootstrap > jQuery > Tether > wowjs
How do I load my vendor files in the correct order?
Success!
webpack.config.js
module.exports = {
entry: {
app: './app.jsx',
vendor: [
"script-loader!uglify-loader!jquery",
"script-loader!uglify-loader!tether",
"script-loader!uglify-loader!bootstrap",
"script-loader!uglify-loader!wowjs",
]
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
],
};
What magic is happening here?
Webpack creates vendor.bundle.js by minifying & bundling my vendor
files which now execute in the global context.
Webpack creates bundle.js with all of its application code
entry file (app.jsx in this case)
import './script';
This script is just custom JavaScript that uses jQuery, Bootstrap, Tether and wowjs. It executes after vendor.bundle.js, allowing it to run successfully.
A mistake I made trying to execute my script.js was that I thought it had to be in the global context. So I imported it with script-loader like this: import './script-loader!script';. In the end, you don't need to because if you're importing through your entry file it will end up in the bundle file regardless.
Everything is all good.
Thanks #Ivan for the script-loader suggestion. I also noticed that the CommonsChunkPlugin was pulling the non-minified vendor versions so I chained uglify-loader into the process.
Although, I do believe some .min.js are created differently to get rid of extra bloat. Though that is for me to figure out. Thanks!
You can try https://webpack.js.org/guides/shimming/#script-loader - it looks like it will execute scripts in order and in global context.
Worked with htmlWebpackPlugin from official tutorials and switched the order form entry key. ( vendor then app )
In webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
vendor: [
'angular'
],
app: [
'./src/index.js',
'./src/users/users.controller.js',
'./src/users/users.directive.js',
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './src/index-dev.html'
}),
new webpack.NamedModulesPlugin()
...
}
Now in the generated index.html file I have the correct order
<script src='vendor.bundle.js'></script>
<script src='app.bundle.js'></scrip
This worked for me https://www.npmjs.com/package/webpack-cascade-optimizer-plugin
const CascadeOptimizer = require('webpack-cascade-optimizer-plugin');
module.exports = {
entry: {
app: './app.jsx',
vendor: ['jquery', 'tether', 'bootstrap', 'wowjs'],
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
new CascadeOptimizer({
fileOrder: ['jquery', 'tether', 'bootstrap', 'wowjs']
})
],
};
Related
I have a static Javascript project (no react, vue, etc.) where I am trying to transpile, bundle, and minify my js with webpack. I would like to have bundle.js on my layout page which will include a bunch of global js that runs on all pages and then a page_x.js file that will be on individual pages as needed. The bundle.js file might consist of several other files and should be transpiled to es5 and minified.
With my current setup, the files are running twice. I'm not sure how to fix this. I want the file included globally but also want to be able to call the function as needed. If I delete the import statement from page.js I get the console error, "doSomething" is undefined. If I only include page.js on page.html and not on _layout.html common.js is only logged out on page.html. I want "common" to be logged once on every page and I want doSomething() to be available only on page.js.
Here is an example of it running twice:
common.js
console.log("common");
export function doSomething() {
console.log("do something");
}
page.js
import {doSomething} from "/common.js";
$(button).click(doSomething);
The expected output on page load (before clicking anything) would be:
"common"
Instead I'm seeing
"common"
"common"
My webpack.config.js file is as follows:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require("webpack-remove-empty-scripts");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const WebpackWatchedGlobEntries = require("webpack-watched-glob-entries-plugin");
const CssnanoPlugin = require("cssnano");
const TerserPlugin = require("terser-webpack-plugin");
const dirName = "wwwroot/dist";
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
output: {
filename: "[name].js",
path: path.resolve(__dirname, dirName)
},
devtool: "source-map",
module: {
rules: [
{
test: /\.s[c|a]ss$/,
use:
[
MiniCssExtractPlugin.loader,
"css-loader?sourceMap",
{
loader: "postcss-loader?sourceMap",
options: {
postcssOptions: {
plugins: [
CssnanoPlugin
],
config: true
},
sourceMap: true
}
},
{ loader: "sass-loader", options: { sourceMap: true } },
]
},
{
test: /\.(svg|gif|png|eot|woff|ttf)$/,
use: [
"url-loader",
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"]
}
}
}
]
},
plugins: [
new WebpackWatchedGlobEntries(),
new CleanWebpackPlugin(),
new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({
filename: "[name].css"
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
})
]
}
};
};
Any help would be greatly appreciated.
Webpack is about building a dependency graph of your application files and finally producing one single bundle.
With your configuration, you are actually trying to use Webpack as a Multi-entry object configuration as explained in Webpack documents. The culprit here is WebpackWatchedGlobEntries plugin. For each file matched by a glob pattern, it would create a bundle which is not what you want ever. For exmaple, if you have following structure:
- src/scripts
- common.js
- some
- page1.js
- other
- page2.js
This plugin will produce multi-page application. So, you configuration:
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
will internally return an object as:
entry: {
"common": "src/scripts/common.js",
"some/page1": "src/scripts/some/page1.js",
"other/page2": "src/scripts/other/page2.js"
}
It means if you import common.js into page1.js and page2.js, then you are in producing three bundles and all those bundles will possess the common module which would be executed three times.
The solution really depends on how to you want to configure your bundle:
If you need to bundle as a multi-page application, then you must use splitChunk optimization that allows you to create page specific bundle while keeping shared code separate (common.js for example). Keep in mind that you do not really need to manually create a separate bundle for common.js. with split chunks, Webpack should do that automatically for you.
If you need a single bundle, you can literally go ahead and create a single bundle for entire application (most typical workflow with Webpack) and use the same bundle on each page. You can have a common function like run that can figure the code to call using URL or some unique page specific identifier. In modern SPA, that is done using routing module.
What I will suggest is to keep things simple. Do not use WebpackWatchedGlobEntries plugin. That will complicate things if you are not familiar with Webpack. Keep entry simple like this:
entry: {
// Note that you don't need common module here. It would be picked up as part of your page1 and page2 dependency graph
"page1": "src/scripts/some/page1.js",
"page2": "src/scripts/other/page2.js"
}
Then, enable the splitchunk optimization as:
optimization: {
splitChunks: {
chunks: 'all'
}
}
Again, there are multiple options to choose from. You can read more details here about preventing code duplication.
I have installed plotly using npm i plotly.js
Added the line import 'plotly.js/dist/plotly' to my plotly import file
Then in webpack followed the instructions here to bundle the files client side.
Added in a custom js file to test plotly
Then added in the plotly scripts to my html page with the package coming first then my custom js.
However I get the error message ReferenceError: Plotly is not defined.
To test I was using the javascript code from this example. I can get it working when I save the file locally found on the plotly site here but not with webpack.
Is there something I am missing or doing wrong? My other packages seem to work fine and I can see plotly.js has successfully been added into the relvent folder client side.
webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
uibundles: path.resolve(__dirname, 'frontend.js'),
plotly: path.resolve(__dirname, 'plotlyimport.js'),
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public/js')
},
plugins: [new MiniCssExtractPlugin({
filename: '../css/[name].css',
})],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.js$/,
loader: 'ify-loader'
},
]
}
};
You probably need to use webpack resolve (here) to add the details
Could you try this:
resolve: {
modules: ['node_modules'],
extensions: ['.js']
},
It seems like you need to use webpack externals to solve this issue.
webpack externals : Prevent bundling of certain imported packages and instead retrieve these external dependencies at runtime.
For example, to include plotly from a CDN instead of bundling it:
index.html
<script src="../plotly.js"></script>
webpack.config.js
module.exports = {
//...
externals: {
plotly: 'plotly'
}
};
This leaves any dependent modules unchanged, i.e. the code shown below will still work:
var Plotly = require('plotly.js');
..
..
Plotly.newPlot('myDiv', data, layout, config );
Refer to webpack externals for more details.
We'd like to have two outputs from Webpack - our entire app with all of its dependencies, and a single different page with only one dependency (that isn't shared by the main app).
It seems the way to do this is to leverage the entry property of a Webpack config. However, that's not enough, as we also use HtmlWebpackPlugin to output our HTML file with the build.js that Webpack compiled dynamically added (as well as compiled LESS, etc). According to the HtmlWebpackPlugin docs:
If you have multiple Webpack entry points, they will all be included
with script tags in the generated HTML.
That won't work for us, so I need to leverage their filterChunks option. This GitHub issue response states it most succinctly:
module.exports = {
entry: {
'page1': './apps/page1/scripts/main.js',
'page2': './apps/page2/src/main.js'
},
output: {
path: __dirname,
filename: "apps/[name]/build/bundle.js"
},
plugins: [
new HtmlWebpackPlugin({
inject: false,
chunks: ['page1'],
filename: 'apps/page1/build/index.html'
}),
new HtmlWebpackPlugin({
inject: false,
chunks: ['page2'],
filename: 'apps/page2/build/index.html'
})
]
};
(in the HtmlWebpackPlugin docs, this is under the "filtering chunks" section)
So, I modified our code like so:
module.exports = {
entry: {
app: './public/js/ide.js',
resetPassword: './public/js/reset_password.js'
},
output: {
path: path.resolve(__dirname, '../build'),
filename: '[name].js',
publicPath: '/'
},
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/html/ide.html',
inject: true,
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'reset_password.html',
template: 'public/html/reset_password.html',
inject: true,
chunks: ['resetPassword']
}),
],
}
Now, when I rebuild the project (just trying with WebpackDevServer for now) and navigate to /index.html, I can see in the network tab the massive bundle file, the contents of index.html (based off the ide.html template), as well as requests for various external resources. However, no actual JavaScript will run (say, a console.log in ide.js). All the HTML in the file shows.
For reset_password.html, all HTML shows, and the reset_password.js file shows, but none of the javascript within runs.
How can I ensure the JavaScript in my entry files runs?
EDIT: I have gotten ide.js working, because I hadn't realized the following was a "chunk":
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
},
So, I added vendor to the index.html HtmlWebpackPlugin chunks property. Now, it looks like this:
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/html/ide.html',
inject: true,
chunks: ['app', 'vendor']
}),
reset_password doesn't need anything in the node_modules folder, and this also doesn't explain why no JavaScript at all would run inside of ide.js, so I'm still quite confused. Also, reset_password is still non-functional.
EDIT2: Looking through the apparently attached reset_password.js file when I load reset_password.html, I can see this line
eval("\n\nconsole.log('DRAGONHELLO');//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9wdWJsaWMvanMvcmVzZXRfcGFzc3dvcmQuanM/ZjY5ZSJdLCJuYW1lcyI6WyJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiOztBQUNBQSxRQUFRQyxHQUFSLENBQVksYUFBWiIsImZpbGUiOiIuL3B1YmxpYy9qcy9yZXNldF9wYXNzd29yZC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIlxuY29uc29sZS5sb2coJ0RSQUdPTkhFTExPJylcbiJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./public/js/reset_password.js\n");
So, clearly my console.log('DRAGONHELLO') is "seen", but I have no idea why it isn't running.
EDIT3: Adding vendor to chunks for reset_password.html causes the JavaScript to run, but I have no idea why, and this is non-ideal because the whole point of the exercise was to have two different bundles, one which was very minimal and didn't need all of our node_modules.
EDIT4: I ran Webpack with profile:true, and I can see I'm not getting the "Chunk Names" wrong:
js/app.3d18b43294ebd54ed083.js 1.34 MiB 0 [emitted] [big] app
js/resetPassword.198485be2b163cc258ed.js 1.02 KiB 1 [emitted] resetPassword
js/2.e7f92193ea3c611a0b36.js 2.23 MiB 2 [emitted] [big] vendor
js/app.3d18b43294ebd54ed083.js.map 2.71 MiB 0 [emitted] app
js/resetPassword.198485be2b163cc258ed.js.map 4.57 KiB 1 [emitted] resetPassword
js/2.e7f92193ea3c611a0b36.js.map 7.12 MiB 2 [emitted] vendor
EDIT5: I tried both
module.exports = {
//...
optimization: {
runtimeChunk: {
name: entrypoint => `runtime~${entrypoint.name}`
}
}
};
and
module.exports = {
//...
optimization: {
runtimeChunk: true
}
};
Based on PlayMa256's comment and the webpack docs on runtimeChunk. Neither caused the JavaScript to execute.
This is a multipart issue.
First, there is a bug in Html-Webpack-Plugin that makes it incompatible with Webpack4 and multiple entrypoints. It must be upgraded to v4.0.0-alpha.2 at least to work.
Second, in the new version, you needn't use use optimization.splitChunks.cacheGroups to manually separate out node_modules. Doing optimization.splitChunks.chunks = 'all' is enough to result in a given entrypoint only getting in its vendors-app-{{chunk_name}} chunk the node_modules it actually imports.
So if you do
optimization: {
splitChunks: {
chunks: 'all'
},
},
Combined with
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/html/ide.html',
inject: true,
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'reset_password.html',
template: 'public/html/reset_password.html',
inject: true,
chunks: ['resetPassword']
}),
]
Combined with
entry: {
app: './public/js/ide.js',
resetPassword: './public/js/reset_password.js'
},
Then, your webpack output will have
app
resetPassword
vendors~app~resetPassword
vendors~app
vendors~resetPassword
Unless you have no imports in your resetPassword.js file, in which case it will look like
app
resetPassword
vendors~app~resetPassword (necessary webpack vendor packages)
vendors~app
More information, images, and conversation at https://github.com/jantimon/html-webpack-plugin/issues/1053
Note that chunksSortMode: is no longer a valid option on the HtmlWebpackPlugin object for the newest version, it is apparently done by default in webpack4.
When you set:
module.exports = {
//...
optimization: {
runtimeChunk: true
}
};
It generates a runtime chunk. What is a runtime? Runtime is where ALL the code that webpack uses to load other files are. This is the heart of webpack when you run build.
Since you have 2 separate bundle files: resetPassword and app.
Ok, you have all the files you need. You maybe need vendors on both too, since vendor in your case contains everything from node_modules. So basically you will have:
html 1: app, vendor, runtimeChunk.
html 2: reset_password, vendor, runtimeChunk.
By doing that, you application should run.
I am trying to load and bundle toastr as a dependency using webpack.
here is the entire webpack config file
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
const DEVELOPMENT = process.env.NODE_ENV === 'development';
const PRODUCTION = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
main: './wwwroot/js/mainEntry.js',
vendor: ['jquery', 'tether',
'bootstrap', 'jquery-colorbox',
'jquery-confirm', 'jquery-validation',
'jquery-validation-unobtrusive',
'toastr', 'jquery.nicescroll',]
},
output: {
filename: '/js/[name].js',
path: path.resolve(__dirname, 'wwwroot'),
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/fonts/&outputPath=/fonts/'
},
{
test: /\.(png|jpe?g|gif|ico)$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/images/&outputPath=/images/'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// (the commons chunk name)
filename: "/js/vendor.js",
// (the filename of the commons chunk)
minChunks: 2,
}),
new ExtractTextPlugin({
filename: 'css/[name].min.css'
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
};
and my entry js file as
//JS
import 'jquery-colorbox';
import 'slick-carousel';
import toastr from 'toastr';
//CSS
import './../../node_modules/bootstrap/dist/css/bootstrap.css';
import './../../node_modules/slick-carousel/slick/slick.css';
import './../../node_modules/jquery-colorbox/colorbox.css';
import './../../node_modules/toastr/build/toastr.css';
import './../../node_modules/jquery-confirm/css/jquery-confirm.css';
import './../../node_modules/font-awesome/css/font-awesome.css';
import './../../Modules/Components/Menu/menu.css';
import './../../wwwroot/css/lahuritv.css';
The bundle is created without any errors. And I can see that the toastr script is included in the bundle when I look at the output bundle. But the problem is that the variable toastr is not available in the browser window.
I tried looking for similar issue but couldn't find any. This is my first time trying to learn webpack. Any help is appreciated.
Simple solution :
//app.js
import toastr from 'toastr'
window.toastr = toastr
Webpack does not expose the modules you import, it bundles your code together with the needed dependencies, but they are still modular and have their own scope. Webpack does not just blindly combine the sources of all the files and certainly does not make everything global. If the entry file you posted is the whole file, then you're not doing anything at all.
Instead with webpack you would do the work in the JavaScript files you bundle. So to speak, all you include in your HTML are the bundles created by webpack, you don't include other scripts that try to access something in the bundle, but you rather do it directly in your bundle.
Of course you could expose it to window by explicitly defining it: window.toastr = toastr after importing toastr, but polluting the global scope is generally not a good idea and it's no different from just including toastr in a <script> tag. If the bundle is just supposed to be used as a library you could have a look at Authoring Libraries. But I think you're just doing a regular web app and you should get all the code together to be bundled by webpack and not rely on it in another <script> tag.
You should go through the Getting Started guide of the official docs. The example is very tiny (creates one DOM element) but shows you the concept of webpack apps and also uses an external dependency.
I'm using webpack with typescript and babel to manage my client side web application.
I want to have a vendor.js file for 3rd party scripts, a main.js file, and per-page scripts I can load as needed to provide specific functionality for a page.
All the scripts are compiling as I would expect, but only the vendor.js file is actually getting invoked. The others are compiled, but never invoked.
Below is my webpack.config.js file.
'use strict';
let webpack = require('webpack');
module.exports = {
entry: {
main: './assets/js/main.ts',
"single-page": './assets/js/src/new-loan.ts',
vendor: [
"svg4everybody"
]
},
output: {
filename: './public/assets/js/[name].js'
},
resolve: {
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity
})
],
module: {
loaders: [{
test: /\.ts(x?)$/,
loader: 'babel-loader!ts-loader',
exclude: /node_modules/
}]
}
}
And an example of one of the page-specific files. Ideally, when loaded, this file would trigger an alert notification on the page.
webpackJsonp([1],[
/* 0 */
/***/ function(module, exports) {
'use strict';
alert('test');
/***/ }
]);
I can see /why/ the alert isn't triggering (the module function is never invoked), but I can't figure out how to configure webpack to work how I'd like it to.
Thanks for the assistance.
I found that the CommonsChunkPlugin is overwriting the vendor bundle in your config. Try changing the name of CommonsChunkPlugin to common from vendor, and include the common bundle in all of your files, before every other bundle. If you use the CommonsChunkPlugin, only the common bundle will contain the core webpack module loader helper functions, so it must be embedded in every page.
new webpack.optimize.CommonsChunkPlugin({
name: "common",
minChunks: Infinity
})