expose-loader doesn't expose modifications to exposed object - javascript

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.

Related

How do I globally declare jQuery inside a Vue-CLI 3 project?

I have seen a lot of questions about this on SO and elsewhere, but so far I had no luck.
For a bit of context, I built a SPA website on a Vue project I created with an "old" command. I don't remember which one but it looked like the following:
vue init webpack <my project>
I recently realized that Vue-CLI 3 would be way easier for me to maintain, keep updated and improve for various contextual reasons, so I installed #vue/cli, created a new project and started to copy/paste files from my old project to the new one.
The old project had a build directory with various webpack config files in it, and I needed jQuery set globally for a package I wanted to use, so I added the following to the "base" config of Webpack.
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
However, with the new project, all I have now as config file is babel.config.js and vue.config.js at the project's root, there are no build nor config directory.
I tried to set jQuery globally with the following lines inside my main.js file:
import jQuery from 'jquery'
window.$ = window.jQuery = jQuery
global.$ = global.jQuery = jQuery
But everytime I reload my page, I get the following message:
jQuery is not defined
So, so far, I use the CDN version of jQuery but I don't feel at ease with this solution.
How should I proceed with a Vue-CLI 3 project?
Thank you in advance
You can use Vue plugins. This allows you to add new property to every single component.
In main.js
import Vue from 'vue';
import jQuery from 'jquery';
Vue.use({
install (V) {
V.$jQuery = V.prototype.$jQuery = jQuery
}
})
Then you can use jQuery everywhere in component.
In MyComponent.vue
import Vue from 'vue'
export default {
mounted () {
this.$jQuery('h1').text('Hello World')
Vue.$jQuery('h1').text('Hello World')
}
}
Even another js file.
In util.js
import Vue from 'vue'
Vue.$jQuery('h1').text('Hello World')
You could go on public/index.html and add the script tag to the body with the route to your jquery file or cdn.
If you put it in a jquery folder in public it would look like:
<body>
<div id="app"></div>
<script type="text/javascript" src="/jquery/jquery-3.4.1.min.js"></script>
</body>
I am not sure if that's exactly what you want but it would make it available globally across your project.
If you are using Jquery plugins, you should do this in your main.js:
/*
* If using Jquery plugins they will expect to be available in the global namespace,
* which isn’t the case when you import/require it. So manually assign jquery to
* window. Use require instead of import because the imports are hoisted to the
* top of a file by the compiler, which would break VueJS code.
*/
const $ = require('jquery')
window.jQuery = window.$ = $;

How to import jQuery only in certain modules with Webpack 4?

I'm using Webpack 4 in a Codeigniter project. A lot of my Javascript code still depends on jQuery (installed through npm), so I have this Webpack configuration:
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
It works, but I'm planning to ditch jQuery, or at least to depend less on it, so instead of importing it globally, I would like to import it only in the modules where it's needed.
I tried removing the above config and added it in a module:
import { $, jQuery } from 'jquery';
import Dropzone from 'dropzone';
When loading the page I receive this error in the console:
myDropzone.js:95 Uncaught TypeError: Object(...) is not a function
at Object.<anonymous> (myDropzone.js:95)
at r (bootstrap:76)
at t (bootstrap:43)
at bootstrap:134
at bootstrap:134
The code causing the error:
$('.dropzone').each(function () { // <--- this line
$(this).dropzone(config);
});
In the console I tried checking for $ and jQuery and the first one works fine, but with jQuery I get an error saying that it's not defined
Ok, I am pretty sure the following happens:
You are loading the jQuery dropzone plugin somewhere high up in the dependencies tree, probably loaded by some other package. The plugin expects to find the jQuery object in the global scope, in order to attach itself to it (hence giving you access to the $().dropzone method, see here ). By using the ProvidePlugin when the dropzone plugin tries to attach itself onto the jQuery object, there is no problem
However since you are now removing the ProvidePlugin the dropzone plugin never manages to attach itself and so there is no $().dropzone method available for use, so you get that error
Long story short, as long as you are depending on external code that assumes that jQuery is loaded in the global scope, you cannot truly eliminate the usage of webpack.ProvidePlugin, unless of course you directly modify the source code of those dependencies.
Try using
import $ from 'jquery';
window.jQuery = $;
window.$ = $;

Including additional jQuery plugins globally with Webpack 4

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.

Confusion over various webpack shimming approaches

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

materialize-css Uncaught TypeError: Vel is not a function

I'm using webpack as my bundler/loader and I can load materialize css in fine (js/css), but when I try to use the toast, it says
Uncaught TypeError: Vel is not a function
I am including the library in the main index.js file by:
import 'materialize-css/bin/materialize.css'
import 'materialize-css/bin/materialize.js'
Does anyone know why this could be happening? Looking at the bundled source, the js for materialize is there.
Had a same problem & came up with somewhat simpler solution:
Only 2 things are needed to be done:
First: Import following in you root module like app.js
//given you have installed materialize-css with npm/yarn
import "materialize-css";
import 'materialize-css/js/toasts';
Second: if webpack, set following Plugin or get the velocity.min.js as global variable just like you would use jquery:
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery': "jquery",
"Vel": "materialize-css/js/velocity.min.js"
}),
I'm also trying to use materialize-css with webpack and have also run into this issue (albeit not for the same reason). Materialize isn't really built with a module loader in mind, and use global variables. They also bundle dependencies into their script directly in a way you might not want in a webpack-workflow.
I have a setup not exactly the same as you but I'll share it anyways, hoping it will help, my webpack+materialize works like this in a file i've created;
/**
* custom-materialize.js
*/
// a scss file where we include the parts I use.
require('./custom-materialize.scss');
/**
* materialize script includes
* we don't use all the plugins so no need to
* include them in our package.
*/
require('materialize-css/js/initial');
require('materialize-css/js/jquery.easing.1.3');
require('materialize-css/js/animation');
// note: we take these from npm instead.
//require('materialize-css/js/velocity.min');
//require('materialize-css/js/hammer.min');
//require('materialize-css/js/jquery.hammer');
require('materialize-css/js/global');
//require('materialize-css/js/collapsible');
require('materialize-css/js/dropdown');
Then just install Velocity from npm npm install velocity-animate
and point the global Vel materialize use to that package instead in webpack.
new webpack.ProvidePlugin({
'$': 'jquery',
'jQuery': 'jquery',
'Vel': 'velocity-animate'
}),
you have to import css and js Files separately in your index.html
you must not import css file in index.js
Make sure that the uglifyJsPlugin is like this.
new webpack.optimize.UglifyJsPlugin({sourceMap: true, mangle: false})
mangle property should be false so that the variable names of your source file doesn't change when you minify.

Categories

Resources