How to access modules bundled from webpack outside bundles/chunks - javascript

Let me first say that I have tried searching for an answer to this and haven't found anything that works or even hints to it being possible.
I recently moved from RequireJS configuration to rolling up with Webpack. In some places in our Groovy app we have have a script tag in a GSP that will access modules through RequireJS like so:
<html>
<body>
...
<script>
require([
'jquery',
'customModule'
], function($, customModule){
// do something with jquery
// ...
// do something with customModule
});
</script>
</body>
</html>
I would need to be able to do the equivalent with Webpack for a few reasons, the most important is our ability to inject Javascript from our admin side to hotfix any production bugs without pushing a maintenance release.
Expecting I should be able to do something like this:
<html>
<body>
...
<script>
webpack_require([
'jquery',
'customModule'
], function($, customModule){
// do something with jquery
// ...
// do something with customModule
});
</script>
</body>
</html>
If you know how to access Webpack bundles from the outside please share! Thanks

Related

Importing JS files outside Webpack

So, i have a script that creates a global object
(function() {
window.GlobalThing = {}
})();
This script will also fill this object attributes
if (!GlobalThing .Resource) { GlobalThing .Resource = require('./Resource'); }
Then, some other scripts that are not bundled with webpack need to use this GlobalThing. I`ve tryed to make it global using ProvidePlugin for example:
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
'GlobalThing': path.join(__dirname,"Path/GlobalThing.js")
})
],
I've tryed adding an Alias as well
resolve: {
alias: {
'GlobalThing': path.join(__dirname,"Path/GlobalThing.js")
}
}
I have tryed doing something like this in the end of my GlobalThing.js file as well:
(function(){
window.GlobalThing = GlobalThing;
})();
Then i had to import to run the script, so i got a random js file inside the webpack to test it:
import GlobalThing from "GlobalThing ";
Still, it seems some scripts cannot seethe GlobalThing. If i bundle everything up with webpack, it works, but i didnt want to as our app is very old and have some very old stuff. Is there a way i can expose GlobalThing to those older scripts ?
ProvidePlugin takes a module name (i.e. string) and internally uses require to load it. So that wont help you in loading your own script.
That said, following should work:
Keep the GlobalThing script out of webpack bundling
Make sure the script itself is copied to the output folder
Make sure the index.html (or whatever html page you are using) has the scripts in the order GlobalThingScript.js followed by webpack bundle.js
To give you more insight:
When webpack bundles the scripts other than GlobalThingScript.js, it doesnt even know there is something called GlobalThing. It just bundles the other files.
That would mean it is upto you to make sure the GlobalThingScript.js is also made it to the final output folder. Also, the html source should use <script> tags to include the GlobalThingScript.js before webpack bundle.js.
In case problem persists, please do edit the OP to include the html source.
Hope that helps.

Why is my global requirejs var undefined?

I have two seperate applications. Both use jQuery an RequireJS. I want to embed application A into application B, so I transfer the required HTML to application B. Application B is an instance of edx-platform (https://github.com/edx/edx-platform).
So far so good, but the second app won't load. First I had both script tags with data-main, but it doesn't work. So I searched and I found this: https://stackoverflow.com/a/10839885
I looks good so far, but my global requirejs variable is undefined. In the HTML edx-platform calls RequireJS with this code:
<script>
window.baseUrl = "/static/";
(function (require) {
require.config({
baseUrl: window.baseUrl
});
}).call(this, require || RequireJS.require);
</script>
<script type="text/javascript" src="/static/lms/js/require-config.js"></script>
<script type="text/javascript">
(function (require) {
require.config({
paths: {
'js/courseware/courseware_factory': 'js/courseware/courseware_factory',
'draggabilly': 'js/vendor/draggabilly',
'js/courseware/toggle_element_visibility': 'js/courseware/toggle_element_visibility',
'js/courseware/course_home_events': 'js/courseware/course_home_events',
'js/courseware/link_clicked_events': 'js/courseware/link_clicked_events',
'moment': 'common/js/vendor/moment-with-locales',
'moment-timezone': 'common/js/vendor/moment-timezone-with-data',
'js/student_account/logistration_factory': 'js/student_account/logistration_factory',
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory',
'js/dateutil_factory': 'js/dateutil_factory',
'js/courseware/accordion_events': 'js/courseware/accordion_events',
'js/bookmarks/views/bookmark_button': 'js/bookmarks/views/bookmark_button',
'js/views/message_banner': 'js/views/message_banner',
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory'
}
});
}).call(this, require || RequireJS.require);
</script>
My code is included later. I tried to run a simple console.log(requirejs) but it didn't work. requirejs is also undefined when calling it from the developer console.
OK I found out why it is undefined. edX also loads the file which undefined all of those variables.
Now I need to find a way to get my code running.

Can we use requirejs in an angular application to manage/modularize only a part of the application?

I have an existing angular web app which doesn't use require.js. I have to create a new business module in the existing application. Can I use require.js for the new module only? So that I don't have to touch the existing code?
The existing index.html looks like this:
<html>
<head>
...
</head>
<body>
...
<script src="http://cdn.gse.site/angular/1.2.9/angular.js"></script>
<script src="js/angular-ui-router.js"></script>
<script src="js/services/angDashboardService.js"></script>
<script src="js/controllers/angDashboardController.js"></script>
<--- More custom scripts here --->
</body>
</html>
I tried including require-main.js in the existing index.html file without removing any of the existing script tags.
The require-main.js looks like this :
require.config({
baseUrl: 'js',
paths:{
'angular' : '...'
},
shim: {
'angular': {export: 'angular' },
'new-module': {
deps: ['angular'], export: 'new-module'
}
}
});
require(['new-module'], function(){});
I am getting the error as following:
Uncaught Error: [ng:btstrpd] App Already Bootstrapped with this Element '<body class="preload ng-scope" ng-app="angDashboard">'
Can we use requirejs in an angular application to manage/modularize only a part of the application?
Yes you can (but why...?). You will be hits by some serious headache if you are not ware of what are you doing
Anyways to able to do this you must be fully understand the concept of angularJS and requireJS .
<script src="http://cdn.gse.site/angular/1.2.9/angular.js"></script>
<script src="js/angular-ui-router.js"></script>
<script src="js/services/angDashboardService.js"></script>
<script src="js/controllers/angDashboardController.js"></script>
This mean you already had an angular app running. So you will not (should not) config or load angular anymore with requireJS
require.config({
baseUrl: 'js',
paths:{
'async-module' : '...'
},
// You won't use 'shim' with this structure
// shim: {}
});
async-module.js
// assuming somewhere you have did this
// var app = angular.module([...]);
//
// NOW you need to convert this 'app' to global variable. So you can use it it requirejs/define blocks
// window.app = angular.module([...]);
define(function(){
window.app.controller('asyncCtrl', function($scope){
// controller code goes here
});
});
Then somewhere inside your app, when you want to load this async-module
requirejs(['async-module'], function(){
console.log('asyncCtrl is loaded!');
});
SUMARY >>
It is possible to do what you asked but it does not very effective. And will be like hell in code management.
If this answer took you lesser than 5 mins to understand, you can give it a try.
If it took you longer than 5 mins to understand. I am highly not recommending you to do this. Using requireJS with angularJS in common way (everything loaded by requireJS) is already complicated and tricky. And this use case even beyond that.

Loading jQuery with RequireJS: different name convention in third-party libraries

I have an application using some third-party libraries as I also have some plug-ins created by myself using jQuery.
The point is: some third-party libraries are using $ and other ones jQuery as naming convention. The way I'm requiring jQuery through RequireJS is just as that:
[...]
var $ = require('jquery');
[...]
This way, I get the following console message as return:
Uncaught ReferenceError: jQuery is not defined jquery.scrolly.js:79
Uncaught TypeError: undefined is not a function
Then, I figured out a candidate solution by creating two variables and requiring jQuery in both:
var $ = require('jquery'),
jQuery = require('jquery');
So, as you can see this "solution" is redundant, unnecessary and unsophisticated — I need something consistent, something better.
Someone can share an idea with me?
Thanks to Daniel A. White, I could use an elegant-way solution called as shim config.
Let's do this step-by-step. My HTML is calling for app.js as require.js wants:
<!DOCTYPE html>
<head>
<!-- ... -->
<script data-main="js/app" src="js/require.js"></script>
</head>
<body>
<!-- ... -->
</body>
</html>
And this is my old js/app.js:
require.config({
baseUrl: 'js',
paths: {
jquery: 'vendor/jquery-2.1.1.min',
modernizr: 'vendor/modernizr-2.6.2.min',
scrolly: 'vendor/jquery.scrolly'
}
});
require(['main']);
Now, here the magic happens — see yourself my new js/app.js:
require.config({
baseUrl: 'js',
paths: {
jquery: 'vendor/jquery-2.1.1.min',
modernizr: 'vendor/modernizr-2.6.2.min',
scrolly: 'vendor/jquery.scrolly'
},
shim: {
'scrolly': {
deps: ['jquery'],
exports: 'scrolly'
}
}
});
require(['main']);
What's the big deal?
The greatness here is the simplicity as RequireJS thinks. As far I could understand, shim config is something like a "dependency manager" for libraries. For example, scrolly is a third-party library dependant of jQuery that have been already loaded — why then should we load it again? There's no need! We just need to inject its usefulness onto scrolly mechanisms that uses jQuery resources.
Another popular example is BackboneJS. Its single hard dependency is UnderscoreJS. To teach Backbone that Underscore is available for use, we supply its dependency through shim config as that:
[...]
shim: {
'backbone': {
deps: ['underscore'],
exports: '_'
}
}
[...]
So, that's it.

require.js, am I doing it right?

I'm new to require.js and found the documentation quite hard to understand. After a while I got my project up and running with the following setup.
project
|
|--js
|--vendor
|--require.js
|--modernizr.js
|--jquery.js
|--modules
|--module1.js
|--module2.js
|--main.js
|--index.html
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<script data-main="/js/main.js" src="/js/vendor/require.js"></script>
</body>
</html>
main.js
define([
"vendor/modernizr",
"modules/module1",
"modules/module2"
]);
modules/module1.js
define(['vendor/jquery'], function () {
// Some module code like
$('#button').on('click', function(){});
});
Is this a good setup if I like to have one import file (main.js) like in a less setup for CSS?
This setup is generally in the right direction, just some slight modifications need to be made.
For the modules, you need to pass in a matching number of arguments for each dependency of the module. In this case, you would want to assign $ to what is returned by the jQuery module, so that you can actually use $ within the module:
define(['vendor/jquery'], function ($) {
// Some module code like
$('#button').on('click', function(){});
});
For main.js, the define() call should be a require() call so that you will be executing whatever's in the module instead of simply registering it a module for some other module to execute:
require([
"vendor/modernizr",
"modules/module1",
"modules/module2"
], function(Modernizr, module1, module2) {
// do something with Modernizr, module1, module2
});

Categories

Resources