Webpack cannot use jQuery - javascript

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

Related

Javascript - How to use a variable across two functions in webpack?

I have to migrate old, plain HTML with plain JS files to webpack but I have troubles when I have a variable declared outside functions and used between multiple functions below them.
One of the example:
// import stuff
var recorder;
var blobs = [];
function recordStart() {
recorder = new MediaRecorder(...);
blobs = []
recorder.ondataavailable = (event) => {
console.log("recording");
if (event.data) blobs.push(event.data);
};
recorder.onstop = function(){...};
recorder.start();
}
function recordEnd() {
recorder.stop();
}
$('#capture_button').on('touchstart mousedown', function() {
recordStart();
})
$('#capture_button').on('touchend mouseup', function() {
recordEnd();
})
However, everytime recordEnd is called, JS console always throw undefined error on recorder object, as if the variable never even touched at all by the recordStart function.
Is there something that I do wrong here? I just learn webpack for a week so please bear with me if this is a rookie mistake.
PS. jQuery runs fine, if I run console.log() in them they would fire properly.
Edit: I forgot to mention this issue happens only after I run npx webpack on it with this configuration:
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif|glb|gltf|usdz)$/i,
type: 'asset/resource',
}
]
},
plugins: [
new CopyPlugin({
patterns: [
{from: "src/images", to: "images"},
{from: "src/maps", to: "maps"},
{from: "src/models", to: "models"}
]
})
]
};
It runs well before I migrate to webpack, but after I bundled with it and include the dist/bundle.js in the HTML in dist/index.html the undefined error happens.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack Title</title>
<script src="./bundle.js"></script>
</head>
<body>
...
</body>
</html>
One of the main selling points of modules is to give your code an explicit dependency chain, and to avoid global variables. For variables that you want to be able to access in other modules, you should export them, and import them where they're needed.
export let recorder;
// do stuff
// assign to recorder
and in the other module
import { recorder } from './theFirstModule';
function recordEnd() {
recorder.stop();
}
The other (bad) option is to make recorder explicitly global, so it'll be accessible anywhere - but that defeats the purpose of using modules and makes code harder to reason about, so I wouldn't recommend it.
Instead of doing
var recorder;
, instead, wherever you assign to recorder, assign to window.recorder. (But if I were you I'd really try working within the module system first)

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.

How to properly split common dependencies with webpack4

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

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.

How to make one bundle from several scripts using webpack?

How to make one bundle from several scripts using webpack? I'm trying to point several entries, but include just last, other just not seen how functions.
That's my app1:
function setCats() {
alert('cat');
}
exports.setCats = setCats;
My app2:
function setDogs() {
alert('dog');
}
exports.setDogs = setDogs;
My index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="bin/app.bundle.js" charset="utf-8"></script>
</head>
<body>
<script>app.setCats()</script>
<script>app.setDogs()</script>
</body>
</html>
My webpack.config.js:
module.exports = {
entry: {
app: [
'./src/app1.js',
'./src/app2.js',
]
},
output: {
path: './bin',
filename: 'app.bundle.js',
library: 'app'
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}]
},
};
After building and running in browser alerted just dog and in console I have this mistake:
index.html:8 Uncaught TypeError: app.setCats is not a function
Thanks :)
It's simple, you can only load one entry point in one webpack output bundle. If you use multiple, they are all loaded but only the last entry point is exported. This is often used to load polyfills before libraries, for example, since they don't export anything anyway.
To do what you want to do, you should simply make another higher-level file where you do this:
var setCats = require('./app1.js').setCats;
var setDogs = require('./app2.js').setDogs;
module.exports = {
setCats: setCats,
setDogs: setDogs,
};
Then simply use only that file as a single entry point (entry: './src/app.js'). After building it the bundled library will work and contain app.setCats and app.setDogs.

Categories

Resources