Webpacking jQuery and custom files: jQuery is undefined - javascript

I'm using webpack to concatenate JS libraries and JS files of my own. Currently the setup is like this
var wpStream = require('webpack-stream')';
var files = ['jquery.js', 'angular.js', 'app.js', 'controller.js', 'etc.js'];
gulp.task('pack', function(){
return gulp.src(files)
.pipe(wpStream({
output:{filename: 'bundle.js'}
}).pipe(gulp.dest('dist'));
});
This works well. My file is created and it has all of the content specified in my files array. However on page load, I get the error jQuery is not defined. Additionally, in my console when I type jQuery there is not jQuery, but rather jQuery111206520785381790835. And when I append a dot to see the list of methods, there's just the normal object methods (hasOwnProperty, toString, etc).
How do I access jQuery? What has webpack done with it?

You have to make jQuery globally accessible with webpack ProvidePlugin
var webpack = require('webpack');
var wpStream = require('webpack-stream');
var path = require('path');
var files = ['app.js', 'controller.js', 'etc.js'];
gulp.task('pack', function () {
return gulp.src(files)
.pipe(wpStream(
{
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.ProvidePlugin({
angular: "angular",
$: "jquery",
jQuery: "jquery"
})
],
resolve: {
root: path.resolve('./vendor'), // directory that contains jquery.js and angular.js
extensions: ['', '.js']
}
}
).pipe(gulp.dest('dist'));
});

Related

webpack, jquery and dynamic form field

I'm using webpack and jQuery in my symfony project.
I configured webpack and jquery as follows :
// webpack.common.js
const path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
main: "./src/default/index.js",
commons: "./src/default/js/app.js"
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /datatables\.net.*/,
use: [{
loader: 'imports-loader',
options: {
additionalCode:
"var define = false;"
}
}]
},
{
test: require.resolve("jquery"),
loader: "expose-loader",
options: {
exposes: ["$", "jQuery"],
},
},
{
test: /\.(scss)$/,
use: [{
// inject CSS to page
loader: 'style-loader'
}, {
// translates CSS into CommonJS modules
loader: 'css-loader'
}, {
// Run postcss actions
loader: 'postcss-loader',
options: {
// `postcssOptions` is needed for postcss 8.x;
// if you use postcss 7.x skip the key
postcssOptions: {
// postcss plugins, can be exported to postcss.config.js
plugins: function () {
return [
require('autoprefixer')
];
}
}
}
}, {
// compiles Sass to CSS
loader: 'sass-loader'
}]
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
}
]
},
resolve: {
alias: {
jquery: "jquery/src/jquery"
}
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
})
]
}
// index.js
import 'bootstrap';
import './scss/app.scss';
import '#fortawesome/fontawesome-free/js/fontawesome';
import '#fortawesome/fontawesome-free/js/solid';
import '#fortawesome/fontawesome-free/js/regular';
import '#fortawesome/fontawesome-free/js/brands';
require('select2');
require('jquery-ui/ui/widgets/datepicker');
require('jquery-ui/ui/i18n/datepicker-fr.js')
import 'datatables.net';
import dt from 'datatables.net-bs5';
import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css';
dt(window, $);
My project use an old jQuery Plugin I made for simplify the use of select2 in my project called select2Helper.
if ( "undefined" !== typeof jQuery ) {
(function($, window, document) {
$(function() {
$.select2Helper = function(element, options) {
// Do stuff
}
// Add the plugin to jQuery functions (jQuery.fn object)
$.fn.select2Helper = function(options) {
// Iteration through the DOM elements we are attaching the plugin
return this.each(function(){
// If the plugin has not already been attached to the element
if (undefined == $(this).data('select2Helper')) {
// Create new instance of the plugin with current DOM element and user-provided options
var plugin = new $.select2Helper(this, options);
// In the jQuery version of the element,
// store a reference to the plugin object
// for access to the plugin from outside like
// element.data('select2Helper').foo(), etc.
$(this).data('select2Helper', plugin);
}
});
}
});
});
}
This plugin was written based on the official jQuery plugin "how to write plugin" documentation.
I load this plugin outside of webpack (old style) so I need to access $ outside of webpack. This scripts use Select2 plugin for initialize autocompletion on select form element.
So far so good.
I'm facing a problem when I want to add dynamically a field that use jQuery select2 Plugin (after page loading).
When I do this (adding new select2 field in my form) and initialize select field with select2 :
if ( "undefined" !== typeof jQuery ) {
(function($, window, document) {
$(function() {
$('.select2-element').select2Helper(config);
});
}(window.jQuery, window, document));
}
The result is : "Select2Helper is not defined."
First test
When I do :
if ( "undefined" !== typeof jQuery ) {
(function($, window, document) {
$(function() {
console.info($.fn);
});
}(window.jQuery, window, document));
}
The result shows me that select2Helper is in the $.fn list ($.fn.select2Helper). But the same code invoked after page loading show me that select2Helper is not in the list.
Second test
When I do on page loading :
if ( "undefined" !== typeof jQuery ) {
(function($, window, document) {
$(function() {
console.info(jQuery);
console.info($);
});
}(window.jQuery, window, document));
}
The return of console.info(jQuery) is not the same as console.info($). In the first I don't have select2Helper in jQuery.fn but it is present in $.fn .
I identified that the $ object used after page loading is same as jQuery object above not $ object above.
Thanks for your help.
I finally found the solution.
My error is not from my jQuery plugin select2Helper or jQuery. Is from a lack of knowledge of webpack and its configuration.
When I Wrote :
// webpack.common.js
const path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
main: "./src/default/index.js",
commons: "./src/default/js/app.js"
},
// ...
}
I defined two entries. For each entries, webpack build two versions of jQuery. My original idea was to integrate a little extra script on top of the rest with some javascript stuff in it. But I realized that it was building a commons.bundle.js with app.js file in it with everything (jQuery, datatable, etc.).
I end up with a file main.bundle.js with jQuery (and other stuffs) in it AND a commons.bundle.js also with jQuery but not the same one, hence the two versions of jQuery.
I copy / paste my little javascript stuffs from app.js directly in index.js and everything work fine now.

Redundant Modules in Webpack Dllplugin

I am using Webpack 3.0.0 for my application. I am trying to split my references to libraries in to separate files using the DllPlugins in Webpack. Initially I was using CommonsChunkPlugin which allowed me to split my libraries as jquery.js and kendo.js . The kendo.js file did not contain the jquery library.
After I changed this using DllPlugin, it seems both jquery.js and kendo.js have jquery.
Below is my configuration
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
jquery: ["jquery"],
kendo: [
'kendo.autocomplete.min',
'kendo.treelist.min',
'kendo.slider.min',
'kendo.tooltip.min',
'kendo.dataviz.chart.min',
'kendo.dataviz.themes.min',
'kendo.grid.min',
'kendo.data.min',
'kendo.core.min'
]
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, '../assets'),
library: "[name]_lib"
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, '../assets/[name]-manifest.json'),
name: '[name]_lib'
})
],
};
Could someone please help change the configuration to exclude jquery from kendo.js .

Automatically loading externals with Webpack

I've done some searching but was wondering if there's an elegant solution here. When building a Webpack app, it's common to have dependencies that don't need to be compiled/bundled, like jQuery, React, ReactDOM, Angular, or Bootstrap, to name a few. You can list these in your Webpack config file in an externals object, but externals just assumes that these libraries will be available as namespaced globals at runtime.
This means that for each entry in your externals hash, you also need to toss in a script tag in your HTML. This makes sense if you're referencing an external CDN, but I'm thinking this could be automated if all you want to do is copy some dist file from a library in node_modules.
I've been looking for examples of how to do this but I haven't seen any yet. I messed with external-loader but I haven't had any luck integrating it (the documentation doesn't seem to provide a complete example).
Essentially, this would need to happen:
Libraries that shouldn't be bundled should be added to resolve.alias, e.g. {"react": "react/dist/react.js"}
A loader copies the dist files to the public directory (maybe this could just be done with file-loader?)
An HTML loader or maybe plugin inserts the script tags before the bundle.js script tag
If something like this doesn't exist, I might look into trying to make one; I'm just posting this here to see if anyone might know of a pre-baked solution, as it seems like it'd be a common problem for building web apps and I figured I'm probably missing something.
var path = require("path");
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: {
'index-ref': './app/index-ref.ts',
'vendor': './app/vendor.ts',
'app': './app/main.ts',
},
resolve: {
extensions: ['', '.ts', '.js']
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'index-ref']
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'template' + '/default.html',
lib: ['jQuery'],
chunks: ['entry-name']
}),
new HtmlWebpackExternalsPlugin([
// Using a CDN for a JS library
{
name: 'jquery',
var: 'jQuery',
url: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js'
}
],
{
basedir: 'node_modules',
dest: 'lib'
}),
new WebpackNotifierPlugin()
]
};
Am I missing anything here?
I didn't find a pre-existing solution, so I wrote a plugin to supplement the HtmlWebpackPlugin. It takes an array of externals and appends script/link tags to the HTML file, generates the externals hash, and can use CDNs or local files.
https://github.com/mmiller42/html-webpack-externals-plugin
If you don't want to add extra package bloat then HtmlWebpackPlugin has templating features, so you could do something like this:
//template.html
<html>
<head>
<%= htmlWebpackPlugin.options.externals %>
</head>
...
</html>
and then something like this in your webpack config:
//webpack.config.js
const EXTERNALS = [
{
name: 'react',
globalVarName: 'React',
src: 'https://cdn.example.com/react#18',
},
...
]
module.exports = {
...,
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'template.html',
externals: EXTERNALS.reduce(
(scripts, external) => (
`${scripts}<script src="${external.src}"></script>`
), ''
)
})
],
externals: EXTERNALS.reduce(
(res, external) => ({
...res,
[external.name]: external.globalVarName,
}), {}
),
}
(You can obviously add any environment customisations/finer details/etc. you want in the webpack config file.)
EDIT:
For some extra panache you could also get the current package versions from your node_modules rather than hard-coding them into the webpack file:
const fs = require('fs')
function getPackageVersion(packageName) {
const pkgPath = `node_modules/${packageName}`
const pkg = JSON.parse(fs.readFileSync(`${pkgPath}/package.json`, 'utf8'))
return pkg['version']
}

webpack require (jquery) won't work

I just started to use webpack and am trying to load jquery synchronously
Here is my main.js
var $ = require('jquery');
require('javascript/index.js');
require('less/index.less');
and here is my webpack.config
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var path = require('path');
module.exports = {
entry: './assets/javascript/main.js',
output: {
path: './assets',
filename: '/javascript/bundle.js'
},
module : {
loaders : [
{
test: /\.css/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")
}
]
},
plugins: [
new ExtractTextPlugin("/css/[name].css")
],
resolve : {
root: path.resolve('./assets'),
extensions: ['', '.js', '.less']
}
};
my index.js looks like this
$(document).ready(function () {
var body = $('body');
var backgrounds = new Array(
'url(./../images/bg1.jpg)' ,
'url(./../images/bg2.jpg)' ,
'url(./../images/bg3.jpg)' ,
'url(./../images/bg4.jpg)'
);
var current = 0;
function nextBackground() {
console.log("Changing bg");
current++;
current = current % backgrounds.length;
body.css('background-image', backgrounds[current]);
}
setInterval(nextBackground, 1000);
body.css('background-image', backgrounds[0]);
});
and on execution throws the error
Uncaught ReferenceError: $ is not defined
I really don't understand this error since if I look into the generated bundle.js Jquery clearly is getting defined.
I already tried to add this to my resolve:
resolve : {
root: path.resolve('./assets'),
extensions: ['', '.js', '.less'],
alias: {
jquery: "jquery"
}
}
but the error is still persistent
Edit: Here is a snipped of the created bundle.js
var $ = __webpack_require__(2);
__webpack_require__(3);
__webpack_require__(4);
According to your code, you need to add this to your index.js
var $ = require('jquery');
That's because when you used the webpack to build your code, each files(e.g index.js) would be wrap into a function which is defined by webpack.
So all the variables defined in your main.js are not accessable to index.js, coz they are now in different function which are not sharing the same scope.
You can either expose jquery to global(window) use the expose-loader or you need to require the jquery manually.
Hope this can solve your problem. : )
What you are looking for is the ProvidePlugin:
Automatically loaded modules. Module (value) is loaded when the identifier (key) is used as free variable in a module. The identifier is filled with the exports of the loaded module.
For example:
Add this plugin to your config:
new webpack.ProvidePlugin({
$: "jquery"
})
Somewhere in your code:
// in a module
$("#item") // <= just works
// $ is automatically set to the exports of module "jquery"
Make sure you got jquery installed via NPM

How to use Webpack with Angular + templateCache?

I'm learning Webpack. I made an App with Angular and I use templateCache to generate all my html views in one js file than require in App. It works cool. But then the Webpack job:
entry: {
app: ["bootstrap-webpack!./bootstrap.config.js", './app/app.js'],
vendor: ['angular', 'bootstrap', 'angular-ui-router', 'oclazyload']
},
output: {
path: path.join(__dirname, "dist"),
filename: '/bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(
/* chunkName= */ "vendor", /* filename= */ "/vendor.bundle.js"),
That was the part of my webpack config. As the result I get directory "dist" with "bundle.js" && "vendor.bundle.js" and index.html. After that I start server and my App says that it can't GET views. Why? :( As I understand all my views have to be bundled and should be available in the "dist" directory.
I do not use the templateCache at all. Since Angular directives also accept a template as string, I just require() the template with the html-loader.
function MyDirective() {
return {
restrict: "E",
replace: true,
template: require("./MyDirective.html")
};
}
// in your webpack.config.js
module.exports = {
module: {
loaders: [
{ test: /\.html$/, loaders: ["html"] }
]
}
}
Its late but might as well share this. If you really want to use html fragments maybe for
<div ng-include="'file.tplc.html'"></div>
here is how I made it work
var appMod = angular.module('app');
appMod.run(function($templateCache) {
function requireAll(requireContext) {
return requireContext.keys().map(function(val){
return {
// tpl will hold the value of your html string because thanks to wepbpack "raw-loader" **important**
tpl:requireContext(val),
//name is just the filename
name : val.split('/').pop()
}
});
}
// here "../" is my app folder root
// tplc is the naming convention of the templates
let modules = requireAll(require.context("../", true, /\.tplc\.html$/));
modules.map(function (val) {
$templateCache.put(val.name, val.tpl);
})
});

Categories

Resources