How to properly split common dependencies with webpack4 - javascript

I am having difficulty configuring webpack4 to properly bundle shared dependencies.
I have two pages in my application (Page1 and Page2). Both require bootstrap, jquery as well as a custom JavaScript app called core.
Page 2 requires the same but also a custom JavaScript application called my-app and also lodash.
Since my core app will be included in all pages, I want to have jquery and bootstrap in the same bundle.
Since lodash is only required for pages running my-app, I want to include that dependency in the my-app bundle.
So I setup my app like this:
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
'core': './src/core/index.js',
'my-app': './src/my-app/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
alias: {
jquery: 'jquery/src/jquery',
}
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
],
mode: 'development',
}
page1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
</head>
<body>
<h1>Page1</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
page2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
<script src="dist/my-app.bundle.js"></script>
</head>
<body>
<h1>Page2</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
(Full project: https://github.com/LondonAppDev/webpack-split-example)
When I run npx webpack, it creates core.bundle.js and my-app.bundle.js, however both of these include jquery.
Is it possible to put all "global" dependencies in core.bundle.js?

Just one thing to remember here, with webpack 4 you don't add vendor scripts as an entry to your webpack.config, just real entry scripts to your application.
WP will create an optimized bundle output for your app using the default settings.
You have to add vendor cache group to your config, in order to extract jQuery, Lodash, Bootstrap,Popper into a separate bundle:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
name: "vendor",
chunks: "all",
enforce: true
}
}
}
},

Related

How to tell web pack to use external js file url without freezing in in bundle?

There is a simple task:
I want to try making a Google Chromecast receiver app (which is SPA). Google Chromecast SDK (cast SDK) requires their framework to be on external url. Also this framework creates global cast object.
What is the correct way of creating this webpack application?
The targets I want to achieve:
Build index.html with HtmlWebpackPlugin
Develop using import this framework (import cast from ???)
Avoid bundling it (probably using externals)?
Ensure cast object created by this js file is global (ProvidePlugin?)
Add <script src="http://cdn....js"></script> into HTML created by HtmlWebpackPlugin
For now I am trying to setup simple app, and I got stuck on last step - adding <script> tag to output html, but I'm sure that there are mistakes I've done on prev steps.
Could you help guiding me through this process?
My current webpack.config.js is:
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
scriptLoading: 'defer',
hash: true,
}) ,
new webpack.ProvidePlugin({
cast: path.resolve(path.join(__dirname, 'src/cast_receiver_framework'))
})
],
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
devServer: {
compress: false,
disableHostCheck: true
},
externalsType: 'script',
externals: {
cast_receiver_framework: ['//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js', 'cast']
}
}
To solve your last step you can use the template param of HtmlWebpackPlugin to customize your template.
By default HtmlWebpackPlugin will inject bundled modules at the end of the <body>.
Check the documentation if you need further customization.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Whatever else you might need -->
</head>
<body>
<div id="your-mount-point-id"></div>
<script src="http://cdn....js"></script>
</body>
</html>
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "path/to/index.html"),
scriptLoading: 'defer',
hash: true,
})
],

Expose a function from a webpack bundle

I'm trying to expose a function to a webpage, that can be called externally, or on window.load, or at anypoint.
DoThing.ts
import * as lib from "libs";
export default function DoAThingFunc():void{
console.log('Do a thing)'
}
This is then imported thing
ExposeDoThing.js
import DoAThingFunc from './DoThing'
window.exposeFunc = DoAThing
window.exposeFunc():
webpack 4 bundle
entry: {
main: './src/MainEntry.tsx',
popupcallback: './src/ExposeDoThing.js'
},
output: {
path: path.join(__dirname, outputDir + "/js"),
filename: '[name].js',
publicPath: "/js/",
library:'pclGlobal'
},
MyPage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pop up</title>
</head>
<body>
<script src="/js/vendor-main.js" type="text/javascript"></script>
<script src="/js/popupcallback.js" type="text/javascript"></script>
</body>
</html>
Nothing is being called, pclGlobal is undefined even though:
var pclGlobal=(window.webpackJsonppclGlobal=window.webpackJsonppclGloba...
is present in the output. And nothing is being called from the functions.
I just want the function DoAThingFunc() to fire when the script has loaded, what am I missing?
I think you need to expose your bundle as a library. check this link about output section in webpack config, and check the right way to do it.
Maybe a config like this: (pay attention to libraryExport attr)
module.exports = {
entry: './index.js',
output: {
path: './lib',
filename: 'yourlib.js',
libraryTarget: 'umd',
library: 'YourLibraryName',
umdNamedDefine: true,
libraryExport: 'default'
}
};
I noticed the vendor-main.js in my quoyted html example, except we don't have one in the weback entry and yet there is file output...
It looks like we used to have a vendors bundle, and then stopped but left the following the webpack.config.js
runtimeChunk: {
name: entrypoint => `vendor-${entrypoint.name}`
}
This has a weird effect. If your entry is not called main, then it wouldn't any execute anyexport default functions.

Webpack cannot use jQuery

I'm starting using webpack (version 3) and when I try to use jQuery from my "app.js" file then nothing happens:
I npm install --save jquery and:
import $ from 'jquery'
$('body').css('backgroundColor', '#DD0000')
document.body.style.backgroundColor = "red";
And when I try changing the css using document.body.style.backgroundColor = "red";
it tells me "cannot read property style of null"
But for the rest it's working, I mean I tried this successfully :
import json from "./test"
console.log(json)
Here is my HTML head part:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack</title>
<link rel="stylesheet" href="./styles.css">
<script src="./dist/bundle.js"></script>
</head>
Here is my webpack config :
const path = require("path");
const uglify = require("uglifyjs-webpack-plugin");
module.exports = {
watch: true,
entry: './app.js',
output: {
path: path.resolve('./dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: ["babel-loader"],
}
]
},
plugins: [
new uglify(),
]
}
Do you know what I'm doing wrong?
You inserted your script in the <head> section. And since JS is blocking, it's executed right there, before the <body> is parsed and exists (so it's null).
If you want this to work, either put your script at the end of the document, right before the closing </body> tag, or wait for the document to be loaded:
window.addEventListener('DOMContentLoaded', function() {
// Here, you can use document.body
});
or, in jQuery syntax:
$(function() {
// Here, you can use document.body
});

Template in HTML, with Webpack, get error "variable" is not defined

I created template in index.html to generate html-code with js, code below. My Webpack configuration also below. When I run it with webpack-dev-server, I get error: title is not defined. Somehow webpack tries to resolve 'title' by self, instead of delegate it to 'lodash/template'. Please help me fix code, I'm in despair(.
import path from 'path';
import glob from 'glob';
import webpack from 'webpack';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const inProduction = process.env.mode === 'production';
export default {
entry: {
app: [
'./src/scripts/main.js',
],
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].[chunkhash].js',
},
module: {
rules: [
{
test: /\.s[ac]ss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'sass-loader'],
fallback: 'style-loader',
}),
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: '/node_modules',
},
],
},
plugins: [
new ExtractTextPlugin('[name].[chunkhash].css'),
new webpack.LoaderOptionsPlugin({
minimize: inProduction,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, './src/index.html'),
}),
],
};
import temp from 'lodash/template'
import data from './data';
const titlePicDiscHalf = temp(document.getElementById('titlePicDiscHalf').innerHTML);
document.write(titlePicDiscHalf({ title: 'Hello World!' }));
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title"><%=title%></div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
</body>
</html>
The problem is that html-webpack-plugin uses the same template tags <%= %> to insert bundle information into template.
You have two options.
1. Change lodash.template delimiters
You could change delimiters used by client-side lodash/template to something else, so Webpack would ignore it. For example:
_.templateSettings.interpolate = /<\$=([\s\S]+?)\$>/g;
Check out this demo.
_.templateSettings.interpolate = /<\$=([\s\S]+?)\$>/g;
const temp = _.template
const titlePicDiscHalf = temp(document.getElementById('titlePicDiscHalf').innerHTML);
document.write(titlePicDiscHalf({ title: 'Hello World!' }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title">
<$=title$>
</div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
2. Change html-webpack-plugin delimiters
Install ejs-loader separately and configure html-webpack-plugin to use it to load your template. There you can change delimiters to yours. It could look something like this:
plugins: [
new HtmlWebpackPlugin({
template: './index.html.ejs',
})
],
module: {
rules: [
{ test: /\.ejs$/, loader: 'ejs-loader', query: {
interpolate: /<\$=([\s\S]+?)\$>/g,
evaluate: /<\$([\s\S]+?)\$>/g,
}},
]
},
Now, you can configure your template with two different set of delimiters, one for client bundle lodash template and another for html-webpack-plugin:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title><$= htmlWebpackPlugin.options.title $></title>
</head>
<body>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title"><%= title %></div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
</body>
</html>
Note, <title><$= htmlWebpackPlugin.options.title $></title> is used by webpack, and <div class="title"><%= title %></div> is by client-side lodash.
Just in case someone else falls in the same trap where one simply wants to use a static html file with some <script> inside and subsequently encountering an error like Referenceerror: variable is not defined.
Make sure you are not using JS template strings!
E.g. instead of doing something like
function addLoginStatus(user) {
document.querySelector('.login-status').textContent = `You logged in as user.firstName`;
}
use something like this:
function addLoginStatus(user) {
document.querySelector('.login-status').textContent = 'You logged in as ' + user.firstName;
}
I found that with template strings, the webpack-html-plugin template will naturally try to interpret them, which was not intended in my case.

Webpack publicPath don't work

I have a demo like this:
my webpack config:
module.exports = {
entry: './app.js',
output: {
filename: 'bundle.js',
path: './build',
publicPath: 'http://localhost:3000/'
},
module: {
rules: [{
test: /static\.html/,
use: 'file-loader'
}, {
test: /\.png/,
use: 'file-loader?name=[name].[ext]'
}, {
test: /\.css/,
use: 'file-loader?name=[name].[ext]'
}],
},
resolveLoader: {
modules: ['node_modules'],
}
}
this is my entry app.js:
import './static.html';
import './img.png';
import './index.css';
static.html:
<!DOCTYPE html>
<html>
<head>
<title>static</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="./index.css">
</head>
<body>
<img src="./img.png">
</body>
</html>
when i run npm run build, i got a build folder. I think the build/static.html should be
<img src="http://localhost:3000/img.png">
<link href="http://localhost:3000/index.css">
But, actually the build/static.html is the same as static.html. Both of the src of img and the href of link are not changed.
Any one knows why?
====
I have known the answer. Webpack publicPath just work for output file. So just the url in the bundle.js will be replaced.
The webpackHtmlPlugin will not resolve this problem, because this plugin will generate a html page with a script links to the output bundle.js that I don't need it.
To resolve this problem, I have wrote a custom loader to transform the html page output by the file-loader dynamicly.
The file-loader would not change the file. If you want a html file with the publicPath added, you should use html-webpack-plugin.

Categories

Resources