Invoke multiple entry points in Webpack - javascript

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
})

Related

How to use a function in one file that was included globally with webpack

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.

Override some Webpack 5 NodeJS modules with polyfill

I have NodeJS code that I now need to move to an embedded system. It takes far too long to simply start NodeJS ("Hello World" ~11sec on a BeagleBone Black) so we needed an alternative. The IoT.js looks promising but it does not support some of the internal NodeJS modules (e.g. url, zlib, tty)--which my code needs. I am using Webpack 5.35.0 to create a single file for my code but this is where my problems lie. I want to use Webpack with a node target since IoT.js offers most of what node offers natively. However is there a way to force Webpack to use polyfills for some of the modules? For example, browserify-zlib instead of expecting nodes zlib.
My basic Webpack configuration is simple:
{ target: 'node10.17',
entry: './index.js',
output:
{ filename: 'index.js',
path: '/work/proj/dist',
libraryTarget: 'umd' },
stats: 'errors-only',
resolve:
{ modules: [ '/work/proj/node_modules' ],
extensions: [ '.js', '.json' ],
}
}
I have done some reading where people claim adding a simple resolve.fallback.zlib = false and resolve.alias should do the trick--which is not working for me.
I tried to simply add resolve.fallback.zlib = false in the hopes to just have zlib omitted from the Webpacked output and this did not work. No matter what I do the standard Webpack boilerplate "node" zlib include code exists.
Standard Webpack boilerplate when using node target.
/***/ "zlib":
/*!***********************!*\
!*** external "zlib" ***!
\***********************/
/***/ ((module) => {
"use strict";
module.exports = require("zlib");;
/***/ })
Other things I tried were--ALL of which did not work:
I was hoping this would alias zlib and actually put in the browserify-zlib code.
resolve:
{ modules: [ '/work/proj/node_modules' ],
extensions: [ '.js', '.json' ],
alias: { zlib: '/work/proj/node_modules/browserify-zlib/lib/index.js' },
fallback: {} } }
Same as the previous example but thought by disabling the fallback the alias/polyfill would go into the output. This is what others online had success with.
resolve:
{ modules: [ '/work/proj/node_modules' ],
extensions: [ '.js', '.json' ],
alias: { zlib: '/work/proj/node_modules/browserify-zlib/lib/index.js' },
fallback: { zlib: false } } }
Here I just hoped to not include zlib to see if Webpack would omit it with a node target.
resolve:
{ modules: [ '/work/proj/node_modules' ],
extensions: [ '.js', '.json' ],
fallback: { zlib: false } } }
Lastly I tried to use the plugin node-polyfill-webpack-plugin but with the node target it does not seem to do anything. If I chose a web target the plugin seems to work as I'd expect (taken from here). Again, I'd prefer a node target so it uses native modules and the setup seems cleaner; but maybe this is the only approach. If this is the approach then how to support fs and other non-browser modules that IoT.js supports natively?
...
plugins = [ new NodePolyfillPlugin({ excludeAliases: [] }) ];
It seems that when the node target is selected there is no way to override any of the default/boilerplate code added to the output file. Does anyone have experience with IoT.js and Webpack, or overriding the default Webpack 5 code for node and use a polyfill instead? Not sure if a Webpack plugin is an approach. I am a little new to Webpack. Could this be a problem with Webpack? Any help would be appreciated.

Get hash of different chunk (into Service Worker) using webpack

I have a web app using a Service Worker to prefetch and cache all assets. So I need a cache key that changes when your assets do, like the hash for the chunk with my assets. However, I struggle to get that hash into the template for the Service Worker.
Here is a simplified version of my webpack.config.js:
const extractSW = new ExtractTextPlugin('serviceworker.js');
module.exports = {
entry: {
main: ['./scripts/main.js'],
serviceworker: ['./templates/serviceworker.js']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'static')
},
module: {
rules: [
{
resource: path.resolve(__dirname, 'templates', 'serviceworker.js'),
loader: extractSW.extract({
use: [
{
loader: 'apply-loader',
options: {obj: {hash: <hash>}}
},
{
loader: 'underscore-template-loader'
}
]
})
}
]
},
plugins: [extractSW, new UglifyJSPlugin()]
};
How do I make <hash> being the hash of the main chunk?
I'd recommend using a Webpack plugin that handles all the intricacies of cache versioning for you, like WorkboxWebpackPlugin (which I work on) or offline-plugin.
You'll end up being able to cache files on a more granular level, for one thing, and only redownload the files that have actually changed (instead of redownloading everything whenever the main chunk's hash changes).

Webpack bundles my files in the wrong order (CommonsChunkPlugin)

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']
})
],
};

How to disable AMD on 4 files and load them in order with webpack

I need to disable AMD on 4 files and load video.js first before loading the other 3 files, because they depend on it. When I tried doing it in webpack.config.js like so:
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: __dirname + '/public',
filename: 'bundle.js'
},
devServer: {
inline: true,
contentBase: './src',
port: 3333
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules|lib/,
loader: 'babel',
query: {
presets: ['es2015', 'react', 'stage-2'],
plugins: ['transform-class-properties']
}
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /[\/\\]lib[\/\\](video|playlist|vpaid|overlay)\.js$/,
exclude: /node_modules|src/
loader: 'imports?define=>false'
}
]
}
}
It doesn't really work, because it just loads video.js (with disabled AMD) and completely ignores the other 3 files.
My folder structure is like so:
▾ lib/
overlay.js
playlist.js
video.js
vpaid.js
▸ node_modules/
▾ public/
200.html
bundle.js
▾ src/
App.js
index.html
main.js
LICENSE
package.json
README.md
webpack.config.js
Now, I found something that takes me 1 step back, because now even video.js doesn't load:
require('imports?define=>false!../lib/video.js')
require('imports?define=>false!../lib/playlist.js')
require('imports?define=>false!../lib/vpaid.js')
require('imports?define=>false!../lib/overlay.js')
and instead just throws these kinds of warnings:
WARNING in ./~/imports-loader?define=>false!./lib/video.js
Critical dependencies:
15:415-422 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ./~/imports-loader?define=>false!./lib/video.js 15:415-422
WARNING in ./~/imports-loader?define=>false!./lib/playlist.js
Critical dependencies:
10:417-424 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ./~/imports-loader?define=>false!./lib/playlist.js 10:417-424
WARNING in ./~/imports-loader?define=>false!./lib/vpaid.js
Critical dependencies:
4:113-120 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ./~/imports-loader?define=>false!./lib/vpaid.js 4:113-120
WARNING in ./~/imports-loader?define=>false!./lib/overlay.js
Critical dependencies:
10:416-423 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ./~/imports-loader?define=>false!./lib/overlay.js 10:416-423
So, my question is, how can I make this work in webpack.config.js so that I don't get these warnings?
I have solved the problem! To make this work you need this:
{
test: /[\/\\]lib[\/\\](video|playlist|vpaid|overlay)\.js$/,
exclude: /node_modules|src/
loader: 'imports?define=>false'
}
and this
require('script-loader!../lib/video.js')
require('script-loader!../lib/playlist.js')
require('script-loader!../lib/vpaid.js')
require('script-loader!../lib/overlay.js')
together!
Now, if you use this (instead of script-loader):
require('imports?define=>false!../lib/video.js')
require('imports?define=>false!../lib/playlist.js')
require('imports?define=>false!../lib/vpaid.js')
require('imports?define=>false!../lib/overlay.js')
It's not gonna work! (you need both imports-loader and script-loader working in unison.

Categories