Hi Jeffrey (and others),
Love your work.
Replacing Bootstrap with Foundation in Elixir works great, but the same swap does not work in Mix. I've spent over 8 hours trying to figure out why before bugging you here.
I've created a simple modification to your laravel-mix-example that illustrates the issue.
Trying to run the resulting js results in the error foundation is not a function, because the Foundation code has not been ran at all (and so $.fn.foundation has not been defined):

As best I can tell, the module loader in Elixir generates strings like so:

It appears to do this with some kind of experimental loader called buble-loader. The eval() call ensures the code gets ran.
Mix, on the other hand, uses perhaps a different Babel loader, resulting in actual code instead of a string:

For some reason, the jQuery plugin is never registered, it's as if it's dealing with a different jQuery instance?
Several others are having the same issue.
Thanks in advance for any guidance.
Were you using mix.webpack with Laravel Elixir?
It looks like this is an issue with Foundation: https://github.com/zurb/foundation-sites/issues/7386
Yeah that's weird. I can't get it to work either. Some odd foundation bug.
If somebody figures out a fix, please post here.
Got the same problem, trying to get it fixed, but no solution so far...
EDIT: I've been trying to try some things from the reply @adriaanzon posted and this seems to work:
import 'what-input';
import 'foundation-sites';
$.fn.foundation = function(method) {
var type = typeof method,
meta = $('meta.foundation-mq'),
noJS = $('.no-js');
if(!meta.length){
$('<meta class="foundation-mq">').appendTo(document.head);
}
if(noJS.length){
noJS.removeClass('no-js');
}
if(type === 'undefined'){//needs to initialize the Foundation object, or an individual plugin.
Foundation.MediaQuery._init();
Foundation.reflow(this);
}else if(type === 'string'){//an individual method to invoke on a plugin or group of plugins
var args = Array.prototype.slice.call(arguments, 1);//collect all the arguments, if necessary
var plugClass = this.data('zfPlugin');//determine the class of plugin
if(plugClass !== undefined && plugClass[method] !== undefined){//make sure both the class and method exist
if(this.length === 1){//if there's only one, call it directly.
plugClass[method].apply(plugClass, args);
}else{
this.each(function(i, el){//otherwise loop through the jQuery collection and invoke the method on each
plugClass[method].apply($(el).data('zfPlugin'), args);
});
}
}else{//error for no class or no method
throw new ReferenceError("We're sorry, '" + method + "' is not an available method for " + (plugClass ? functionName(plugClass) : 'this element') + '.');
}
}else{//error for invalid argument type
throw new TypeError(`We're sorry, ${type} is not a valid parameter. You must use a string representing the method you wish to invoke.`);
}
return this;
};
$(document).foundation();
It feels a bit like cheating, but works for the time being.
Hmm... are we sure that Foundation is the issue? I can also cause issues with just jQuery.
If you set up your app.js as just:
window.$ = window.jQuery = require('jquery');
And then set up your template's scripts like this:
<script src="{{ mix("js/app.js") }}"></script>
<script>
jQuery(function() {
alert('hello');
});
</script>
It also throws an undefined error:

It seems that alterations to the window object are sometimes not being respected.
@ItsRD Thanks for the hack - while that seems to get Foundation running, I found it impossible to actually bind to any events like the modal open event and whatnot, no matter what I tried.
Everything I do in Elixir "just works", whereas most event-driven / jquery binding stuff doesn't work in Mix no matter what I try. For now I'm going to revert to using Elixir.
Here's an example of the hack above not working:
$.fn.foundation = ...
$(document).foundation();
$('#modal').bind('open.zf.reveal', function() {
alert('This alert will never show, no matter what you try.');
});
$(document).on('open.zf.reveal', '#modal', function() {
alert('This alert will also never show, no matter what you try.');
});
jQuery plugins can be confusing with Webpack - usually because they're legacy plugins that assume there will be a global jQuery object available to work with.
What's odd is how amazingly well Elixir's webpack works, even though it seems to be dependent on that "experimental" JS loader.
@JeffreyWay What do you think about perhaps using the same webpack JS loader that Elixir uses (buble-loader) in Mix?
@JVMartin - Maybe. I like Buble a lot. I'm not sure if it's related in this case, though.
We're you using mix.webpack() with Laravel Elixir? Or mix.scripts()?
Webpack has always been confusing, when it comes to jQuery plugins. But I'd like to get this all figured out so that people don't even have to think about it.
I was able to get it working by:
mix.autoload({}) to your webpack.mix.js file.npm install script-loaderimport 'script-loader!jquery';
import 'script-loader!foundation-sites';
$(document).foundation();
But, yeah, it really sucks that this is required. There's a big thread on Foundation's GitHub about it: https://github.com/zurb/foundation-sites/issues/7386
Interesting. I am using mix.webpack(). I actually tried script-loader early on in my experiments, but was missing the mix.autoload({}) which is probably why I had no success with it.
Here's what's working for me, using Elixir:
gulpfile.js
elixir(mix => {
mix.webpack('app.js');
mix.sass('app.scss');
mix.version(['js/app.js', 'css/app.css']);
});
resources/assets/js/bootstrap.js
// Window-bound objects.
window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
window.Vue = require('vue');
window.axios = require('axios');
window.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest'
};
// Other packages.
require('foundation-sites');
require('any-other-jquery-plugin');
resources/assets/js/app.js
require('./bootstrap');
$(document).foundation(); // Works great
$('#modal').bind('open.zf.reveal', function() { // Works great
alert('Works great!');
});
Everything I throw at Elixir's webpack like this - jquery plugins, etc. - all work great without any additional tweaking.
I know next to nothing about converting ES6 to ES5, I wish I could be of more assistance in figuring out what's going wrong in Mix.
When I look at the differences between Mix's compiled ES5 and Elixir's compiled ES5, I'm even more confused. Elixir generates those strings as in my screenshots in my original post until you use --production, in which case it does actually generate real code without encasing it in eval()s, but in both cases it works great.
@JeffreyWay Thanks for the mix.autoload({}) tip. That worked perfectly for me, from a fresh Laravel 5.4 install with Laravel Mix.
For others, my relevant files are below (with a little experiment to prove the event binding works).
boostrap.js
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
window.$ = require('jquery');
window.jQuery = window.$;
/**
* Vue is a modern JavaScript library for building interactive web interfaces
* using reactive data binding and reusable components. Vue's API is clean
* and simple, leaving you to focus on building your next great project.
*/
window.Vue = require('vue');
/**
* Require Foundation JS
*/
require("foundation-sites");
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest'
};
app.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('example', require('./components/Example.vue'));
const app = new Vue({
el: "#app",
mounted(){
$(document).foundation();
$("#dropdown-pane").on("hide.zf.dropdown", () => {
console.log("Dropped down");
});
}
});
webpack.mix.js
const { mix } = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
// Prevents Mix's automatic configuring of jQuery, so that we
// can set it up ourselves later
// https://github.com/JeffreyWay/laravel-mix/issues/229#issuecomment-276230983
mix.autoload({});
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
This works great but for some reason when running the production code this fails...
@AndyBursh Can you check if running yarn run production works or fails for you?
Thanks!
@amosmos I can't, sorry. I don't have a Mix project to test it on.
Most helpful comment
@JVMartin - Maybe. I like Buble a lot. I'm not sure if it's related in this case, though.
We're you using
mix.webpack()with Laravel Elixir? Ormix.scripts()?Webpack has always been confusing, when it comes to jQuery plugins. But I'd like to get this all figured out so that people don't even have to think about it.
I was able to get it working by:
mix.autoload({})to yourwebpack.mix.jsfile.npm install script-loaderBut, yeah, it really sucks that this is required. There's a big thread on Foundation's GitHub about it: https://github.com/zurb/foundation-sites/issues/7386