webpack, jquery and dynamic form field - javascript

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.

Related

Override what's bundled in webpack based on file extension

I'm trying to figure out whether or not webpack can do something like this. I have some code that I want to be bundled for a specific device. So I created a ViewFactories.ios.tsx and I also have ViewFactories.tsx. The problem i'm encountering is that the if I ignore the .ios.tsx in the loader test, it still gets bundled. I'm using the ignore-bundler plugin to ignore the .ios.tsx files, but the reference is just empty. ie:
/** still getting loaded here: **/
const ViewFactories_ios_1 = __importDefault(__webpack_require__(/*! ./ViewFactories.ios */ "./build/app/src/modules/layouts/factories/ViewFactories.ios.tsx"));
/*** the referenced section, but blank now ***/
/***/ "./build/app/src/modules/layouts/factories/ViewFactories.ios.tsx":
/*!***********************************************************************!*\
!*** ./build/app/src/modules/layouts/factories/ViewFactories.ios.tsx ***!
\***********************************************************************/
/*! dynamic exports provided */
/*! all exports used */
/***/ (function(module, exports) {
/***/ }),
What I really want is the reference to ViewFactories.tsx instead of ViewFactories.ios.tsx.
Is there some sort of dependency graph in webpack that I can access to tell the loader to use the default instead of the .ios.tsx?
My webpack config:
{
test: (modulePath) => {
if (/\.ios\./.test(modulePath)) {
console.log(modulePath);
return false;
}
return /\.tsx?$/.test(modulePath);
},
loader: "awesome-typescript-loader",
options: {
configFileName: 'tsconfig.web.json',
transpileOnly: true,
errorsAsWarnings: true,
}
},
{
test: (modulePath) => {
if (/\.ios\./.test(modulePath)) {
console.log('Ignored: ', modulePath);
return true;
}
return false;
},
loader: 'ignore-loader'
},
Just build two bundles, with shared config options for whatever is the same?
// webpack.config.js for two different bundles
let sharedModuleDef = {
rules: ...
}
let sharedPlugins = ...
let iosBundle = {
entry: "src/ios.entry.js",
output: {
path: ...
filename: "ios.bundle.js"
},
module: sharedModuleDef,
...
};
let everythingElse = {
entry: "src/main.entry.js",
output: {
path: ...
filename: "standard.bundle.js"
},
module: sharedModuleDef,
...
};
module.exports = [iosBundle, everythingElse];

Webpack 2: shimming like RequireJS for jQWidgets?

I’m migrating from a RequireJS project to Webpack.
The latter is new to me, I’m using this as a learning exercise.
In RequireJS I could register stuff like this:
shim: {
'jqxcore': {
exports: "$",
deps: ["jquery"]
},
'jqxtree': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxbutton': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxsplitter': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxmenu': {
exports: "$",
deps: ["jquery", "jqxcore"]
}
}
and then just require “jqxsplitter” for example like so:
import "jqxsplitter"
and stuff would be correctly registered and loaded.
Now I was looking at a couple of guides/tutorials/takes I found on migrating from RequireJS to Webpack, such as this one and this one.
So following those insights I’m trying something like this in my webpack.config.js:
"use strict";
// Required to form a complete output path
const path = require("path");
// Plagin for cleaning up the output folder (bundle) before creating a new one
const CleanWebpackPlugin = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
// Path to the output folder
const bundleFolder = "./wwwroot/";
// Path to the app source code
const appFolder = "./app/";
module.exports = {
// Application entry point
entry: {
main: appFolder + "index.ts",
vendor: [
"knockout",
"jquery",
"jqxcore"
],
jqxsplitter: "jqxsplitter"
},
// Output file
output: {
filename: "[name].js",
chunkFilename: "[name].js",
path: path.resolve(bundleFolder)
},
module: {
rules: [{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/
}, {
test: /\.html?$/,
loader: "html-loader" //TODO: file-loader?
}],
loaders: [{
test: /jqxcore/,
loader: "imports?jquery!exports?$"
}, {
test: /jqxsplitter/,
loader: "imports?jquery,jqxcore!exports?$"
}]
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
alias: {
"jqxcore": "jqwidgets-framework/jqwidgets/jqxcore",
"jqxsplitter": "jqwidgets-framework/jqwidgets/jqxsplitter"
}
},
plugins: [
new CleanWebpackPlugin([bundleFolder]),
new HtmlWebpackPlugin({
filename: "index.html",
template: appFolder + "index.html",
chunks: ["main", "vendor"]
}),
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "vendors.js",
minChunks: Infinity
})
],
devtool: "source-map"
};
the relevant part (I assume) being
module: {
loaders: [{
test: /jqxcore/,
loader: "imports?jquery!exports?$"
}, {
test: /jqxsplitter/,
loader: "imports?jquery,jqxcore!exports?$"
}]
},
It’s pretty clear how the syntax of “imports/exports” is supposed to be the equivalent of RequireJS’ “deps” and “exports”.
However when I do this in my index.ts file (app root):
import "jqwidgets-framework/jqwidgets/jqxsplitter";
I get the “jqxBaseFramework is undefined” error when running my app.
I’ve found references to this error on the forums of jQWidgets, but none of the answers seem to REALLY tackle the issue or include things like the AOT compilation, which doesn’t apply to my situation because I’m not using Angular.
I've posted this same question on the jQWidges forums, but so far no actual answer (going on two weeks now), only a single generic answer saying I should load jqxcore.js before jqxwhateverplugin.js.
Well yes, obviously, that's what I'm trying to accomplish using the shimming after all.
Any ideas?
Well I ended up deep diving and figuring it out for myself.
Here's the solution should anyone find themselves in the same or a similar boat.
If you beautify the jQWidgets script files jqxcore.js, you'll see it creates a what would normally be a global variable called "jqxBaseFramework", which will of course never be exposed globally, only within its own module. And there lies the problem.
The solution is to use this configuration:
module: {
rules: [{
test: /jqxcore/,
use: "exports-loader?jqxBaseFramework"
}, {
test: /jqxknockout/,
use: ["imports-loader?jqxBaseFramework=jqxcore,ko=knockout", "exports-loader?jqxBaseFramework"]
}, {
test: /jqxsplitter/,
use: "imports-loader?jqxBaseFramework=jqxknockout"
}]
},
resolve: {
...
alias: {
"knockout": "knockout/build/output/knockout-latest",
"jqxcore": "jqwidgets-framework/jqwidgets/jqxcore",
"jqxknockout": "jqwidgets-framework/jqwidgets/jqxknockout",
"jqxsplitter": "jqwidgets-framework/jqwidgets/jqxsplitter"
}
},
I guess once it clicks, this all makes sense.
The jqxcore module will now export its jqxBaseFramework variable with the same name.
I added in knockout support while at it.
jqxknockout expects two global variables to work normally: ko (knockout) and jqxBaseFramework.
So now we tell webpack that whenever jqxknockout is loaded, it should load the jqxcore module and assign its export to a module-local variable called "jqxBaseFramework" and load the knockout module and assign its export to a module-local variable called "ko".
This effectively equates to prepending the following code to the jqxknockout.js script:
var jqxBaseFramework = require("jqxcore");
var ko = require("knockout");
The script can now execute again because those two variables are found.
I added the export loader to export the same, but now processed/augmented jqxBaseFramework variable from jqxknockout.
jqxSplitter normally only needs jqxCore to work, but I want to use it with knockout, always. So instead of importing jqxBaseFramework from jqxCore for jqxSplitter, I'm getting it from jqxKnockout, so all the pieces are in place.
So now when I add this code to whatever file I'm in:
import "jqwidgets-framework/jqwidgets/jqxsplitter";
Webpack will require jqxknockout and its export for it, being jqxBaseFramework, which in turn will require jqxcore and knockout et voilà, the whole thing is wired up beautifully.
Hope this helps someone!

Making a node_module global using Webpack 2

I am using the below package on an angular project:
https://github.com/pablojim/highcharts-ng
A requirement is that it needs highcharts as a global dependency, in which it tells you to add a script tag into your html:
<script src="http://code.highcharts.com/highcharts.src.js"></script>
Rather than add the above script tag into my HTML I would like to make it global via Webpack.
I have installed highcharts via npm and have tried using the ProvidePlugin and the noParse methods described here (to no avail): https://webpack.js.org/guides/shimming/#scripts-loader
For the ProvidePlugin option I used:
new webpack.ProvidePlugin({
Highcharts: "highcharts",
})
for noParse:
noParse: [
/[\/\\]node_modules[\/\\]highcharts[\/\\]highcharts\.js$/,
],
Neither worked, meaning when highcharts-ng tried to work, it gets an error because it cannot create a new Highcharts:
TypeError: Cannot read property 'Chart' of undefined
// from highcharts-ng which throws above error
chart = new Highcharts[chartType](mergedOptions, func);
Here is my angular module
import angular from 'angular'
import highchartsNg from 'highcharts-ng'
import { ReportsData } from './reports.data'
import { reportsWidget } from './reports-widget/component'
export const ReportsModule = angular
.module('reports', [
highchartsNg,
])
.factory('ReportsData', ReportsData)
.component('reportsWidget', reportsWidget)
.name
My webpack config:
var webpack = require('webpack')
module.exports = {
context: __dirname + '/app/modules',
entry: {
vendor: ['angular', 'highcharts-ng'],
modules: './modules.js',
},
output: {
path: __dirname + '/.tmp/modules',
filename: '[name].bundle.js',
},
plugins: [
// info: https://webpack.js.org/guides/code-splitting-libraries/
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'manifest'],
}),
],
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['es2015'] },
},
],
},
],
},
}
Use expose-loader and write require('expose-loader?Highcharts!highcharts'); somewhere before the first usage of Highcharts global variable. This will require highcharts and save expose it as window.Highcharts in the browser.
Under the hood webpack makes it available for you as:
/* 0 */
/***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["Highcharts"] = __webpack_require__(1);
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))
/***/ }

Webpacking jQuery and custom files: jQuery is undefined

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

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

Categories

Resources