I'm learning Requirejs and I started with two simple .html pages: index.html and second.html.
On the index.html I worte:
<script data-main="assets/js/app.min" src="js/vendor/require.js"></script>
The app.min.js file look like this:
requirejs.config({
baseUrl: 'js/vendor',
paths: {
app: '../app',
jquery: 'jquery-1.10.1.min'
}
});
requirejs(["app/main"]);
My app/main.js file has just a jQuery alert:
define(['jquery'], function($) {
$(function() {
alert('Hello World');
});
});
It works fine!
Now I'm worried just about one thing... What about if I need to load the app/main globally for all my pages and then another file like app/second that run only on second.html page?
Probably I'm missing something about Requirejs... I don't thinks that I need to load everything on the app.min.js file like did for the app/main.
I understand that I can define modules on separate js files but then how can I manage different files for different pages without loading everything in just one file? Probably I'm wrong, I hope you can open the light in my brain for that.
Thanks
I understand that a page might need its own code in addition to what is in app.min. You could do something like this:
<script data-main="assets/js/app.min" src="js/vendor/require.js"></script>
<script>
// You can call the config function as many times as you need to add new configuration.
requirejs.config({
// Presumably, baseUrl does not need to be changed.
// baseUrl: 'js/vendor',
paths: {
// additional paths you may need
}
});
// This loads the code proper to this page.
requirejs(["app/second"]);
</script>
If app/second depends on app/main make sure to have that dependency listed in app/second's define call.
Take a look at this example: https://github.com/requirejs/example-multipage. The example demonstrates how you can create page1.js and page2.js and in those files load the common stuff + page specific things. That's one of several ways to do it.
Another way to do which is what I often use is putting something like this in all your pages:
<script src="require.js"></script> <!-- just require -->
<script src="app.min.js"></script> <!-- your config and also loading the main module -->
and then on second.html, you would also add this
<script>require(["app/second"])</script>
You can use this setup for development, and for production you can replace the first 2 lines with just <script src="optimized-bundle.js"></script>. The optimized-bundle.js could include require.js + config + app/main + app/second. Or if you want to load app/second only on the second.html in production to make your main script smaller, you can have require.js + config + app/main in the primary bundle and optimize app/second into a separate bundle - the html would stay the same in both cases.
Hope this helps.
Related
tl;dr; how should you load multiple webpack bundle without littering your code with script tags
I've been looking into using webpack more however there seems to be a piece i'm missing when it comes to loading webpack bundles, up to now i've been using requirejs which allows you to split you code into modules (which i consider the equivalent of bundles in webpack) but requirejs allows you to require scripts by name in your code which is then mapped to a url in the config but with webpack you would just include a script tag onto the page which feels like it could get out of control quite easily as you would end up with scripts littered throughout the views of your application, and makes it more difficult to switch out bundles as instead of updating a single url you need to find and replace every occurance of that url, not the end of the world but it seems like i've either missed some functionality to make this easier or maybe it's an accepted difference between requirejs and webpack.
I should mention the none of the code bases i'm considering adding webpack too are single page applications so perhaps webpack is just not suited to this kind of environment?
Just to add a little bit of context to what i would intend this to be included in, our server side code uses a mvc pattern so it would look something like this
page skelton/layout
<html>
<head><!-- stuff here --></head>
<body>
<!-- some included view here -->
</body>
</html>
view 1
<div>
<!-- ... -->
<!-- Currently it has this -->
<script>
require(['something'], function(s){ /* new s(); */ /* s(); */ /* etc */ });
</script>
<!-- and i'd imagine it would be like this with webpack -->
<script src="my_bundle.js"></script>
</div>
view 2
<div>
<!-- ... -->
<!-- Currently it has this -->
<script>
require(['something', 'another_thing'], function(s){ /* new s(); */ /* s(); */ /* etc */ });
</script>
<!-- and i'd imagine it would be like this with webpack -->
<script src="my_bundle.js"></script>
<script src="my_bundle2.js"></script>
</div>
Recently I used webpack's code splitting functionality in my singe page application to dynamically load bundles based on the route. This doesn't require you to litter script tags throughout your application necessarily. If you use a routing mechanism of any sort you can dynamically import the dependency when that route is accessed like so:
// Index
router.on(() => {
import(/* webpackChunkName: "routeA" */ '../routes/routeA').then((module) => {
// do something with your loaded module
}).resolve();
});
Dynamically loading this 'root' style module, i.e. the root of a dependency tree bundled by webpack means you can only fetch it when you need to. This means the client will only fetch this bundle.js when you execute this route.
Using the dynamic imports requires the addition of the chunkFilename property in your webpack config:
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
Edit: If you're using express.js you could do the same following its routing mechanism. Your best bet is to refer to the code splitting documentation provided by webpack (linked above).
I wasn't entirely happy with the router approach as it would have effectivly meant having to duplicate our url rewriting config in javascript and duplicating import statements where pages share code and i'm not sure but the import docs seem to imply that any imported scripts would be included in the final bundle instead of dynamically loaded during page load which would mean a lot of code in the bundle that is unused most of the time.
it seems like the best approach for me would be to effectively create a simpler version of requirejs to load bundle files (example below) which would then allow me to keep a similar structure to the existing code, admittedly i might still look for a library instead of rolling my own loader but i haven't decided yet.
Loader
class Loader{
constructor(config){
this.config = config;
}
load(modName){
if(this.config.debug)console.log('loader - ', 'load called', arguments);
if(!document.getElementById(modName)){
if(this.config.debug)console.log('loader - ', 'loading new script', modName, this.config.map[modName]);
var script = document.createElement('script');
script.id = modName;
script.type = 'text/javascript';
script.src = this.config.map[modName];
script.async = true;
document.head.appendChild(script);
}
}
}
export default Loader;
Loader Config
window.loader = new Loader({
'debug': true,
'map': {
'something': '/scripts/bundle.js',
'another_thing': '/scripts/bundle2.js'
}
});
View 1
<div>
<!-- ... -->
<!-- Currently it has this -->
<script>
require(['something'], function(s){ /* new s(); */ /* s(); */ /* etc */ });
</script>
<!-- which would change to (any of the callback code would be inside the bundle) -->
<script>
window.loader.load('something');
</script>
</div>
How can I call requireJS require(['app'], function() {}); only once at the beginning for the whole application so that any subsequent require(["..."], function(...){}); don't need to be wrapped within require(['app']?
This is my set up:
1) Load require.js
<script data-main="js/app.js" src="requirejs/require.min.js"></script>
2) Have app.js shims and basUrl configured properly.
requirejs.config({
baseUrl: "scripts/js",
paths: {
"jquery": "../bower_components/jquery/dist/jquery.min",
"modernizr": "../bower_components/modernizr/modernizr",
.
.
.
},
shim: {
"jquery.migrate": ['jquery'],
.
.
.
}
});
3) Dynamically load JS on different pages:
// Home Page
require(['app'], function() {
require(["jquery", "foundation", "foundation.reveal"], function ($, foundation, reveal){
$(document).foundation();
});
});
// Catalog Page
require(['app'], function() {
require(["jquery", "lnav/LeftNavCtrl","controllers/ProductCtrl", "controllers/TabsCtrl"], function ($, NavCtrl, ProductCtrl, TabsCtrl){
$(function() {
NavCtrl.initLeftNav();
});
});
});
Unless I wrap with require(['app'], function()) each time I call require("...") to load external JS or AMD modules, the app is not initialized and I get JavaScript errors. The above code works but it's not very efficient.
Is there a way to start my requireJS app before I try loading scripts?
I tried calling at the very beginning right after I load require.min.js:
require(["app"], function (app) {
app.run();
});
but it didn't work.
There are no provisions in RequireJS to ensure that a specific module is always loaded before any other module is loaded, other than having your first module load the rest. What you are trying to do is share your first module among multiple pages so it cannot perform the work of loading what is specific to each page.
One way you can work around this is simply to load app.js with a regular script element:
<script src="requirejs/require.min.js"></script>
<script src="js/app.js"></script>
Then the next script element can start your application without requiring app.js:
<script>
require(["jquery", "foundation", "foundation.reveal"], function ($, foundation, reveal){
$(document).foundation();
});
</script>
This is actually how I've decided to launch my modules in the applications I'm working on right now. True, it is not as optimized as it could be because of the extra network round-trip, but in the case of the applications I'm working on, they are still in very heavy development, and I prefer to leave this optimization for later.
Note that generally you don't want to use script to load RequireJS modules but your app.js is not a real module as it does not call define, so this is okay.
Another option would be to use a building tool like Grunt, Gulp, Make or something else and create one app.js per page and have each page load its own app.js file. This file would contain the configuration and the first require call to load the modules specific to your page.
My program starts through this entry point in a twig template:
<script data-main="{{ asset('bundles/wwwcms/js/app') }}" src="{{ asset('bundles/wwwcms/js/require.js') }}"></script>
This outputs:
<script data-main="static.correct_require_url.net/js/app" src="static.correct_require_url/require.js"></script>
That all works fine, so require executes my app.js (config):
requirejs.config({
'baseUrl': 'js/lib',
'paths': {
'app': '../app',
'jquery': '//code.jquery.com/jquery-1.10.2.min',
'bootstrap': '//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min'
},
'shim': {
'bootstrap': {
deps: ['jquery']
}
}
});
// always require jquery and bootstrap onload
requirejs(['bootstrap']);
Which also works.
The directory structure is as follows:
app.js
require.js
/lib
- test.js
/app
- dirty.js
- import.js
Okay so here is where the error comes
For a page, let's say I want to load dirty.js and import.js. dirty.js depends on jquery, and import.js depends on "test.js" (something I just made up for demonstration purposes).
So I load the dependencies on the page this way:
<script type="text/javascript">
require(['app/dirty', 'app/import']);
</script>
When the page loads, dirty.js and import.js are loaded in just fine, from the correct url relative to "app". However, the dependency for import.js (test.js) does not get loaded correctly.
This is how import.js starts:
require(['jquery', 'test'], function($, test) {
...
});
As you can see, it's asking for lib/test.js. It pulls jQuery from a CDN, so that's taken care of. But for test.js, it uses the wrong path.
import.js (and all other modules loaded first) are pulled in using the correct static url path, but dependencies for modules are loaded using the wrong path- they are loaded using the url of that page!
So when I look at the network tab in the debugger console, import.js comes from:
static.blahblah.com/js/app/import.js.
but test.js comes from:
blahblah.com/route/to/current/page/js/app/test.js
(which obviously 404s every time)
What are possible reasons for this occurring? Am I doing something wrong? Any help would be greatly appreciated.
This is how the script tags look before require runs:
<script data-main="//static.bananajams.cloud.net/bundles/wwwcms/js/app" src="//static.bananajams.cloud.net/bundles/wwwcms/js/require.js"></script>
<script type="text/javascript">
require(['app/dirty', 'app/preview', 'app/import']);
</script>
Basically data-main is only a good idea if it's the single entry point for your code. It's loaded asynchronously, so there is no guarantees it runs before the other code in your app. See
RequireJS does not run data-main script before loading required modules
for more info on this.
I’m developing a multi-page app, using requirejs to manage my javascript libs / dependencies.
My idea is that i'll have a main.js that holds the config, and then an .js file for each page that needs it, for example "register.js"
My require config is in javascripts/main.js
requirejs.config({
baseUrl: '/javascripts',
waitSeconds: 200,
paths: {
'async': 'lib/require.async',
'jquery': 'lib/jquery-1.7.2.min',
'knockout': 'lib/knockout-3.0.0'
});
I’ve got a knockout view model that looks like this:
javascripts/viewModels/userDetailsViewModel.js
define(['knockout'], function(ko) {
return function() {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
});
My ‘entry point’ is javascripts/register.js
require(['./main', 'knockout', 'viewModels/userDetailsViewModel'], function(main, ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
On my register.html page, i’ve got the script reference like this:
<script data-main="/javascripts/register" src="/javascripts/lib/require.js"></script>
When my page loads, I get these errors in the console:
GET http://localhost:3000/javascripts/knockout.js 404 (Not Found)
and
Uncaught Error: Script error for: knockout
I’m not sure why it’s looking for knockout.js - I’ve specified knockout in the paths section of my config, to look in lib/knockout-3.0.0
My dir structure is:
javascripts/
Most of my pages js files go here
javascripts/viewModels
Has knockout viewmodels
javascripts/lib
Contains knockout, jquery, requirejs etc...
The problem is that RequireJS will execute the call require(['./main', 'knockout', 'viewModels/userDetailsViewModel'] without a configuration. Yes, ./main is listed before knockout but there is no order guarantee between the dependencies passed in a single require call. RequireJS may load ./main first, or knockout first. And even if ./main were loaded first by this specific call, I believe it would not have any impact on how the other modules loaded by this call would load. That is, I think this require would operate on the basis of the configuration that existed at the time it was called, and that any configuration changes caused by the modules it loads would take effect only for subsequent require calls.
There are many ways to fix this. This should work:
require(['./main', function(main) {
require(['knockout', 'viewModels/userDetailsViewModel'], function(ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
});
Or you might want to restructure your files and what you pass to data-main so that your requirejs.config is loaded and executed before your first require call. Here's an example of restructuring. Change your entry point to be /javascripts/main.js:
<script data-main="/javascripts/main.js" src="/javascripts/lib/require.js"></script>
Change /javascripts/main.js so that it contains:
requirejs.config({
baseUrl: '/javascripts',
waitSeconds: 200,
paths: {
'async': 'lib/require.async',
'jquery': 'lib/jquery-1.7.2.min',
'knockout': 'lib/knockout-3.0.0'
});
require(['knockout', 'viewModels/userDetailsViewModel'], function(ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
And remove /javascripts/register.js. This would be one way to do it. However, it is hard for me to tell whether this would be what you want in your specific project, because I do not know the whole project. The way to restructure for your specific project really depends on what other pages might use RequireJS, what information is common to all pages, what is specific to each page, whether you use a template system to produce HTML, etc.
Hi i'm using requirejs to just organize my javascript codes into AMD modules, i'm building a non-single page website right now using codeigniter, what i do is i have a default layout html file which i always call to render the dynamic content of the body so my script which call the requirejs and has the data-main attribute is encoded on my layout html page.
<script data-main="<?=base_url('assets/js/main.js');?>" src="<?=base_url('assets/js/require.js');?>"></script>
and my main.js looks like this.
requirejs.config({
baseUrl: "assets/js",
paths: {
jquery: 'vendor/jquery',
bootstrap: 'vendor/bootstrap',
datatables: 'vendor/jquery.dataTables'
},
shim: {
jquery: { exports: '$' },
datatables: { deps: ['jquery'] }
}
});
requirejs(['bootstrap','datatables'], function(){
})
so when i type on my url "localhost/ci-project/" it will load the layout page together with the dynamic body. On this scenario it works fine. sicne requirejs will render the path correctly "localhost/ci-project/assets/js/vendor/[js file/module]"
but when i change the URL to say, 'localhost/ci-project/welcome/admin'. what requirejs do to load the modules is concatenate the baseUrl + module path, to the current URL which in this case is 'localhost/ci-project/welcome/admin' and ending result is like this:
'localhost/ci-project/welcome/admin/assets/js/vendor/[module]' which is wrong.
so how can i configure requirejs to always load the from the root url and then concatenate the baseUrl value together with the paths of each modules?
The way to solve this is to output the baseUrl from a server side variable.
In your PHP page:
<script>
var require = {
baseUrl: '<?= base_url(); ?>' // Or whatever outputs the full path to your site root
};
</script>
<script data-main="<?=base_url('assets/js/main.js');?>" src="<?=base_url('assets/js/require.js');?>"></script>
Creating a require object before RequireJS is loaded allows you to define config options that will be picked up automatically.
Calling require.config() again from inside main.js is fine, it will just add the additional config options, but be sure to remove baseUrl from there.
Simon's answer above helped when I had issues with navigating to routes with parameters - relative paths to base url go wrong. I am using asp .net mvc, but the solution is exactly the same:
(_Layout.cshtml)
<script>
var require = {
// by default, load any module IDs from
baseUrl: '#Url.Content("~/Scripts/lib")'
}
</script>
<script data-main="#Url.Content("~/Scripts/application/app.js")" src="#Url.Content("~/Scripts/lib/require.js")"></script>