Webpack script-loader vs. imports-loader?module=>false - javascript

I'm trying include jquery (and other legacy libs) into a webpack project.
I need the jquery to be available globally, also in HTML.
There are so many options to achieve this, but here are 2 I came up with:
Option 1.
// webpack.config.js
module: {
noParse: /jquery/,
rules: [
{
test: /jquery/,
use: ['imports-loader?module=>false']
}
]
},
// entrypoint.js
import 'jquery';
Option 2.
// webpack.config.js
module: {
rules: [
{
test: /jquery/,
use: ['script-loader']
}
]
},
// entrypoint.js
import 'jquery';
Which of these would be better (Or some other method?)
Both of these seem to work identically, however I think option 1 would be better since script-loader (option 2) uses eval.

Related

How to call imported jquery functions in dev tools console? [duplicate]

I want to expose the jQuery object to the global window object that is accessible inside the developer console in the browser. Now in my webpack config I have following lines:
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
These lines add the jQuery definitions to each file in my webpack modules.
But when I build the project and try to access jQuery in the developer console like this:
window.$;
window.jQuery;
it says that these properties are undefined...
Is there a way to fix this?
You need to use the expose-loader.
npm install expose-loader --save-dev
You can either do this when you require it:
require("expose?$!jquery");
or you can do this in your config:
loaders: [
{ test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' }
]
UPDATE: As of webpack 2, you need to use expose-loader instead of expose:
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: '$'
}]
}]
}
The ProvidePlugin replaces a symbol in another source through the respective import, but does not expose the symbol on the global namespace.
A classic example are jQuery plugins. Most of them just expect jQuery to be defined globally. With the ProvidePlugin you would make sure that jQuery is a dependency (e.g. loaded before) and the occurence of jQuery in their code would be replaced with the webpack raw equivalent of require('jquery').
If you have external scripts relying on the symbol to be in the global namespace (like let's say an externally hosted JS, Javascript calls in Selenium or simply accessing the symbol in the browser's console) you want to use the expose-loader instead.
In short: ProvidePlugin manages build-time dependencies to global symbols whereas the expose-loader manages runtime dependencies to global symbols.
Looks like the window object is exposed in all modules.
Why not just import/require JQuery and put:
window.$ = window.JQuery = JQuery;
You will need to ensure that this happens before requiring/importing any module that makes use of window.JQuery, either in a requiring module or in the module where it's being used.
This always worked for me. including for webpack 3 window.$ = window.jQuery = require("jquery");
None of the above worked for me. (and I really don't like the expose-loader syntax). Instead,
I added to webpack.config.js:
var webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
}
Than all modules have access through jQuery through $.
You can expose it to the window by adding the following to any of your modules bundled by webpack:
window.$ = window.jQuery = $
Update for Webpack v2
Install expose-loader as described by Matt Derrick:
npm install expose-loader --save-dev
Then insert the following snippet in your webpack.config.js:
module.exports = {
entry: {
// ...
},
output: {
// ...
},
module: {
loaders: [
{ test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery" }
]
}
};
(from the expose-loader docs)
In my case works
{ test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" }
Update for Webpack v2
After webpack 5 upgrade, you could face this warning.
[DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING] DeprecationWarning: Using a string as loader options is deprecated (ruleSet[1].rules[7].use[0].options)
Simply change the options to
options: {
exposes: ["$", "jQuery"],
}
will look like this:
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
{
exposes: ["$", "jQuery"],
}
}]
}]
}

Named functions in a javascript file using Webpack 4

I just started using Webpack 4 in a project and am new to Webpack. Once Webpack 4 was implemented, I noticed that named functions kept erroring saying [functionName] is not defined.
I have looked quite extensively over the last few days and have tried multiple options with no success. I am hoping for someone to help me work through this in a more direct fashion way.
function openNav(obj) {
...do something
}
#foreach (object in list)
{
...create some HTML
}
const bundleFileName = 'bundle';
const dirName = 'wwwroot/dist';
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: [
'./src/Index.js',
'./src/css/site.css',
'./src/js/app.js'
],
output: {
filename: bundleFileName + '.js',
path: path.resolve(__dirname, dirName)
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
}
]
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: bundleFileName + '.css'
})
]
};
};
I expected the button in the razor file to send the object over to the named function so something can happen like it did previously before webpack
You've said you're trying to use openNav from an onclick attribute. The problem there is that openNav has to be a global to be used that way, but Webpack is a module bundler. Functions at the top level of modules aren't globals. (Which is a good thing.)
The solution is to not use onxyz-attribute-style event handlers. Instead, use modern event handling (addEventListener, probably with at least some event delegation).
To ease your transition from the old way to the modern way, you can expose a function globally from within a module like this (on browsers):
window.openNav = openNav;
I strongly recommend only doing that temporarily to make it easier to transition to modern event handling.
thats because your webpack config isnt set to understand how to handle javascript files, add this to your rules key:
{
test: /\.jsx?/,
loader: 'babel-loader'
}
you will also need to install babel loader that is in a version compatible with your babel core, for v7^ you need to install #babel/core #babel/loader etc, otherwise, babel-loader etc

Extending AngularJS with jQuery in a TypeScript module

While trying to implement TypeScript with my codebase, I run into this problem. It seems that the way I used to load jQuery and AngularJS in sequence, AngularJS would pick up on the presence of jQuery and extend itself with its functionality. However, when importing locally in a module, AngularJS gets loaded in isolation and cannot seem to extend itself with jQuery. Therefore, when I do something like this:
import * as $ from 'jquery';
import * as angular from 'angular';
export default ['$window', function($window) {
let position = angular.element($window).scrollTop();
}];
I get this TypeScript error:
Property 'scrollTop' does not exist on type 'JQLite'.
How can I load AngularJS in a way that it knows it can use jQuery and will extend itself with it?
if you are using webpack then we can use expose-loader library to load jQuery to window object.
webpack v1 usage
module: {
loaders: [
{ test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery" },
]
}
webpack v2 usage
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}]
}
for more details goto expose-loader
Based on the link #ParthRaval shared, I was able to fix the problem by typecasting the JQLite base to an 'any' type. So indeed it turned out to be a typecasting problem rather than AngularJS not picking up on loading and using jQuery in full. Wrapping it in a function makes it less ugly.
import * as $ from 'jquery';
import * as angular from 'angular';
export default ['$window', function($window) {
let position = JQLiteFix(angular.element($window)).scrollTop();
function JQLiteFix(query) {
return <any>query;
}
}];

Webpack fast import rule

I need to use webpack with snapsvg. According to snapsvg's documentation, to import it with webpack, I need to use a loader:
const Snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);
So, in every Javascript file I use, I need to write this thing. I would like to avoid it.
Aliased import
Is there a way, maybe in webpack.config.js, to specify that every time this is encoutered:
import snap from "snapsvg"
Then that line is translated into the one mentioned before? So I can avoid typing the same thing in every file.
You can use this rule:
Install Imports Loader (npm i -D imports-loader) and add this to your webpack config:
module: {
rules: [
{
test: require.resolve('snapsvg/dist/snap.svg.js'),
use: 'imports-loader?this=>window,fix=>module.exports=0',
},
],
},
resolve: {
alias: {
snapsvg: 'snapsvg/dist/snap.svg.js',
},
},
With that you can import snap from "snapsvg" without hassle.

How To Use ScrollMagic with GSAP and Webpack

In order to use ScrollMagic with GSAP, you need to load the animation.gsap.js plugin. With Webpack you would do something like this to accomplish that (assuming you use the CommonJS syntax and installed everything with npm):
var TweenMax = require('gsap');
var ScrollMagic = require('scrollmagic');
require('ScrollMagicGSAP');
To make sure that this actually works, you have to add an alias to your Webpack configuration, so that Webpack knows where the plugin lives.
resolve: {
alias: {
'ScrollMagicGSAP': 'scrollmagic/scrollmagic/uncompressed/plugins/animation.gsap'
}
}
Unfortunately, ScrollMagic keeps throwing an error, when you are using this configuration and the CommonJS syntax like above.
(ScrollMagic.Scene) -> ERROR calling setTween() due to missing Plugin 'animation.gsap'. Please make sure to include plugins/animation.gsap.js
The Solution
You have to tell Webpack to stop using the AMD syntax by adding the following loader that deactivates the define() method.
// Webpack 4+
module: {
rules: [
{ parser: { amd: false }}
]
}
// Webpack <= 3
// Don’t forget to install the loader with `npm install imports-loader --save-dev`
module: {
loaders: [
{ test: /\.js$/, loader: 'imports-loader?define=>false'}
// Use this instead, if you’re running Webpack v1
// { test: /\.js$/, loader: 'imports?define=>false'}
]
}
Why?
The problem lies in the fact that Webpack supports the AMD (define) and CommonJS (require) syntax. That is why the following factory script within plugins/animation.gsap.js jumps into the first if statement and fails silently. That is why setTween() etc. are never added to the ScrollMagic Constructor.
By telling Webpack not to support the AMD syntax (using the loader mentioned above), the plugin jumps into the second if statement correctly, embracing the CommonJS syntax.
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['ScrollMagic', 'TweenMax', 'TimelineMax'], factory);
} else if (typeof exports === 'object') {
// CommonJS
// Loads whole gsap package onto global scope.
require('gsap');
factory(require('scrollmagic'), TweenMax, TimelineMax);
} else {
// Browser globals
factory(root.ScrollMagic || (root.jQuery && root.jQuery.ScrollMagic), root.TweenMax || root.TweenLite, root.TimelineMax || root.TimelineLite);
}
I hope this prevents other people from spending a whole evening trying to figure out what is going on.
The solution I came across that doesn't require you to alter your webpack.config.js file and actually works for me can be found here: https://github.com/janpaepke/ScrollMagic/issues/665
The gist of it is to make sure you have ScrollMagic and GSAP added via npm (hopefully that's obvious) as well as imports-loader:
npm install --save scrollmagic gsap
npm install --save-dev imports-loader
Then in the file you want to use ScrollMagic with GSAP do the following imports:
import { TimelineMax, TweenMax, Linear } from 'gsap';
import ScrollMagic from 'scrollmagic';
import 'imports-loader?define=>false!scrollmagic/scrollmagic/uncompressed/plugins/animation.gsap';
Using Webpack 4.x and imports-loader 0.8.0
medoingthings solution has since changed syntax to include "-loader" suffix.
module: {
loaders: [
{ test: /\.js$/, loader: 'imports-loader?define=>false'}
]
}
https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
In imports-loader 1.1.0, the syntax of the configuration has changed a bit, so now you have to use the following to get the ScrollMagic plugins to work:
{
test: [
path.join(config.root, '/node_modules/scrollmagic/scrollmagic/uncompressed/plugins/jquery.ScrollMagic.js'),
path.join(config.root, '/node_modules/scrollmagic/scrollmagic/uncompressed/plugins/debug.addIndicators.js')
],
use: [
{
loader: 'imports-loader',
options: {
additionalCode: 'var define = false;'
}
}
]
}
Hopefully this helps others.
I was having the same issue and found this question.
For those using Webpack 5 I believe imports-loader is out of date so according to the webpack docs add this code to your js rule to disable AMD:
{
test: /\.js$/,
include: /node_modules/,
parser: {
amd: false
}
}
documentation: https://webpack.js.org/configuration/module/#ruleparser

Categories

Resources