I am trying to make a jQuery plugin accessible to inline JavaScript using Webpack 4.
I am using the PluginProvider to make jQuery available to my website:
plugins: [
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery": "jquery"
}),
],
This is working fine and I can access jQuery from any page that includes my bundle.
I tried to add bootstrap-datepicker by creating a bundle called vendor.js with the following contents:
import 'bootstrap-datepicker';
I can call $('input').datepicker() from within the vendor.js bundle, however if I try and call it using an inline <script> I get:
Uncaught TypeError: $(...).datepicker is not a function
How can I configure Webpack 4 to make bootstrap-datepicker available to the global scope?
UPDATE
I've uploaded the sourcecode demonstrating this issue here: https://github.com/LondonAppDev/webpack-global-jquery-issue
It appears the issue is that the second bundle import is re-adding jQuery without the datpicker add-on. Is there a way around this?
I've gone a few rounds with this type of issue and had the most success with the expose-loader. In your webpack config you should set up a section for jQuery using the following expose loader configuration:
module.exports = {
module: {
rules: [
{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}, {
loader: 'expose-loader',
options: '$'
}]
},
...
]
}
}
There is a similar SO posts here:
How to import jquery in webpack (their regex pattern did not work for me)
Expose jQuery to real Window object with Webpack
Webpack 2 loading, exposing, and bundling jquery and bootstrap
You should be able to find several other articles/posts using this configuration, it is the only one that I have successfully been able to get to work to date.
Also of note, bootstrap 4 seems to also load or do a require on jQuery internally, so if you include an import or require after your jQuery import/require and plugins, it will reinit jQuery and cause your plugins to lose scope.
Related
I'm trying out Webpack for the first time on one of my old website. The website has JQuery installed via CDN.
On one of my js file, I need to have Fancybox js plugin so I import as below
import { fancybox } from "#fancyapps/fancybox";
import "#fancyapps/fancybox/dist/jquery.fancybox.min.css";
I executed "npm run dev" and webpack gave me error "Can't resolve "jquery". So I have to include Jquery as a plugin in my webpack.config.js as follow
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
}),
],
Execute "npm run dev" again and it works as expected. My question is how to avoid duplicate jquery loading since the website template already include JQuery via CDN and now my webpack output file also include JQuery? I tried to remove the Jquery CDN but then it caused my other js files to break. Thanks.
I found the solution to my issue. Adding it here for anyone who has similar issue in the future. Instead of importing jquery into webpack, I set webpack config to use external jquery as follow
externals: {
jquery: "jQuery",
},
I'm setting up a project (typescript, webpack) with a couple of js libraries, configured as externals in webpack. They should not be part of the bundle, instead provided by script tags within the html.
But when trying to use them in my class, they resolve to undefined.
Fabric configured as an external in webpack is resolving to undefined
An error occurs when trying to set up the fabric js library as an external within a (typescript + webpack ) project. Fabric should not be bundled in the output file since it will be the responsibility of the consumer to provide (eg. through a browser script tag).
Note: jQuery initially had an issue (as an external) but is now resolved, and works as expected. Fabric on the other hand does not.
fabric has been configured as an external so that it will not be included in the webpack bundle.
Here's how...
Added as an external within the webpack.config.js
...
externals: {
jquery: 'jQuery',
fabric: 'fabric',
},
...
Installed the declaration files for both libraries
npm install #types/jquery -D
npm install #types/fabric -D
Added the libraries in public folder and index.html (since they must not be part of the app bundle)
<script src="js/lib/jquery.min.js"></script>
<script src="js/lib/fabric.min.js"></script>
Created a class App.ts, imported and implemented instances of these two libraries. (see App.ts)
import { fabric } from "fabric";
import $ from 'jquery';
fabric resolves to undefined within the class App.ts with the error:
TypeError: Cannot read property 'Canvas' of undefined
Please don't recommend ProvidePlugin or installing Babel.
More about webpack "externals": https://webpack.js.org/configuration/externals/
Update #1
jQuery is now working as an external library. I was not referencing the actual jquery global "jQuery" in the externals setup. I had "JQuery" (with a capital J). That's now resolved and jquery is working. Thanks #Aluan
Fabric on the other hand seems to be a different issue altogether.
What you're looking for is called shimming. Webpack docs cover this extensively here: https://webpack.js.org/guides/shimming/
Edit to add example:
In your webpack.config.js plugins array:
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
EDIT:
I pulled down your code and got it working. Here are the steps:
ts-loader chokes on shims, so use babel's #babel/preset-typescript -- otherwise you'll need to find a way to tell the ts compiler to ignore them. This will get you started:
npm install --save-dev #babel/core #babel/cli #babel/preset-env #babel/preset-typescript core-js#3
In your root, create a file called .babelrc and add the following:
{
"presets": [
"#babel/preset-typescript",
[
"#babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3"
}
]
]
}
Add this to your webpack.config.js:
plugins: [
new ProvidePlugin({
$: "jquery",
jQuery: "jquery",
fabric: "fabric"
})
]
Also update ts-loader, changing it to babel-loader.
Now in your code, you'll need to prefix your shimmed libraries with window:
constructor(private readonly selector: string, canvasHeight: number, canvasWidth: number) {
window.$(`#${selector}`).replaceWith(`<canvas id="${selector}" height=${canvasHeight} width=${canvasWidth}> </canvas>`);
this.canvas = new window.fabric.Canvas(`${selector}`, { selection: false });
}
It turns out that the issue with fabric is from fabric itself! The reason fabric is resolving to undefined (when being configured as an external on webpack) is related to the way that fabric exposes its library for consumption. It's an issue they need to fix.
I've added an issue on the official fabric github page
But there is a quick solution for us. Just import using CommonJS like this:
const fabric = require('fabric');
Now it works!
I'm using Webpack 2, Bootstrap 3, and TypeScript, and attempting to integrate npm and packaged bundles into an existing application. I'm using ProvidePlugin to make jQuery available, and expose-loader to expose jQuery to external dependencies.
(Any combination of (<any> global).$ = global.jQuery = $; or webpackmodule: { rules [{}] } configurations wouldn't work, but eventually I got the following to work:
plugins: ([
// make sure we allow any jquery usages outside of our webpack modules
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
jquery: "jquery",
"window.jQuery": "jquery",
"window.$": "jquery"
}),
]),
entry.ts:
import "expose-loader?$!jquery"
import "expose-loader?jQuery!jquery"
However, when I then try and call import "bootstrap" I can call $(...).popover() within my module, and I can call $(...) or jQuery(...) outside the module, but I can't call $(...).popover outside the module, instead I get this error:
Uncaught TypeError: $(...).popover is not a function
How do I make methods that are added to jQuery (like the bootstrap popover method) available in the global scope, outside of my modules?
I found my issue:
PluginProvider was exposing a different version of jQuery to the application than expose-loader was exposing. Bootstrap was initializing on the PluginProvider jQuery, but a different instance of jQuery was being exposed to the window.
So to make it work, delete PluginProvider and just use the expose-loader. And manually import jQuery where you need it as a side-effect of losing PluginProvider.
I'm a little confused on the various ways webpack allows to expose a variable that isn't available on npm or to be put in the bundle. I was able to expose the google visualizations chart script's global google var by using
resolve: {
extensions: ['.js', '.json'],
alias: {
'google': path.resolve(__dirname, 'vendor', 'google.js')
}
}
combined with
plugins: [
new webpack.ProvidePlugin({
'google': 'google'
})
]
however looking at the webpack docs there a couple other ways to shim, which look like they might do something similar. There is imports-loader and exports-loader, and script-loader. I know that I've linked to the docs, but I still find their descriptions of when these four should be used a bit unclear.
Also looking at this example, is this require not assigned to a variable? Where is it meant to go? And where is the documentation on what is going on with this syntax?
require("imports?$=jquery!./file.js")
Can someone provide me some examples of when each of these should be used?
scripts-loader
I never used this myself, but the idea is simple I guess. I think it can be used if for some reason you want to inject a script or a function or something in one of the modules/files you have no control over them.
imports-loader & exports-loader
In one of the apps I worked on we had to use tinymce which in its older versions was dependent on this being always window because it was built to work as a global script. Not as a CommonJS or ES module.
So in order to fix that, we had to use the import-loader so it can inject window to the script. Here how it looked like in webpack.config.js
{ test: require.resolve('tinymce/tinymce'), use: ['imports?this=>window', 'exports?tinymce'] }
Which says inject window in place of this & also we are using exports-loader here so we can export the global tinymce as a default export named tinymce so we can use it as a normal module in our app.
Thankfully all of this is already fixed in the latest releases.
ProvidePlugin
In my experience, this is useful when a library is depending on another library being in a global scope or something. Like jQuery plugins for example, they do use one of these $, window.$, jQuery & window.jQuery
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.$': 'jquery',
'window.jQuery': 'jquery',
}),
So what this plugin will do is to make sure when webpack sees one of these variations it will provide the jQuery object to it instead.
The difference between this & imports-loader for example that you might not know which variation is used by which script. So you let webpack handle this while the imports-loader is kind of more specific.
I hope this helped you a bit to understand the differences between all of them, also this is the new documentation page which I think better than the one you were checking https://webpack.js.org/guides/shimming/
imports and exports loaders are very simple to understand. If you use one of them, or both, your module is wrapped into another function with exports and imports.
For example, I'm using paho-mqtt module meant to be used like global <script src=""> on the page:
import Paho from 'imports-loader?this=>window!exports-loader?Paho!paho-mqtt';
//and this is transformed by webpack to something like:
(function(window){
//wow you can use `window here`, `this` in the global context === window.
// original module code here
// that exposes global var `Paho`
module.exports = Paho;
})(this);
This might be a basic question but I'm really new to react and webpack.
How do I go about adding normalize.css and css frameworks?
I don't want to use bootstrap, instead I found a lightweight css framework called concise css. I'd like to use this since i'm mainly after the positioning and formatting of my elements and have more flexibility with my styles.
I've already installed css-loader and styles-loader.
I've read you can load styles to components by using
require("./path/to/css")
However, i'm still really confused by this.
You can put your require("./path/to/css") inside of your index.js or any other top-level React component. It will get bundled in your web pack for all child components from there.
Make sure you have your loaders installed in your package.json file and youur webpack.config.js file calls the loaders. A barebones webpack.config.js file that accomplishes this might look like:
module.exports = {
entry: [
'./app/index.js'
],
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{test: /\.css$/, loader: 'style-loader!css-loader'}
]
}
}
Upon further reading, I realized normalize.css is very static and webpack works with more dynamic files.
So instead, I just got the cdn for normalize.css and use semantic-ui-react