I'm moving from requirejs to webpack.
In my require js setup, I have a site-wide file site.js that does some global setup, and non-page-specific stuff, like ajax prefilters, some global ui component setup, etc.
Some pages have self contained js apps specifically for a given page, where I'd have window.appPaths = ['some/path/to/app.js'] set in the template, and site.js would load up the app once the initial site wide stuff was done.
However, moving to webpack, it's reading my require(window.appPaths) and then it converts it to some regex and attempts to bundle every single js file in my src directory.
So basically, I'm wondering if there's a way for me to set this up to have multiple entry points, one being something like site.js and another being something like somePageApp.js wherein site.js is actually a dependency of the latter, but the latter doesn't always exist.
Sigh, hopefully that's clear, i'll elaborate where necessary.
edit: This is how it was set up in require.js setup, my output was:
html for an app page:
<script src="site.js">
<script>
window.appPaths = ['someApp.js'];
</script>
site.js:
require([
// deps for sitewide stuff
], function () {
// doing global general stuff
someTask(function () {
if(window.appPaths) require(window.appPaths)
})
})
someApp.js:
require([
// app specific deps
], function () {
// application code that doesn't run because not `require`d until the site.js tasks are done.
})
My "ideal" setup isn't necessarily the exact same, but I would want someApp.js to not load and/or run until site.js loaded. In my requirejs setup, site.js was also essentially the commons chunk but I don't think that's necessary here... just having trouble finding a parallel style of organization in webpack...
Related
I'm starting a new angular project from a scratch and I want to avoid using require.js, browserify or webpack entirely. I'm gonna put all my code into separate files - no monolithic controllers.js or directives.js files...
Few options that are left are:
Use <script> tags and always put new files at the bottom (downside I might mess up order of the files at some point)
Use concatination mechanism, i.e. gulp-concat or grunt-concat
Both of them are ugly - first is maintenance nightmare and the second requires build step and file watcher setup...
The closest thing that I could use is require.js but I'm really disgusted with how the code looks after combining angular with require:
define([], function () {
angular.module('app').controller('BlaBlaController', function () { ... });
})
Since angular is handling all the DI for me require.js is used only for file loading which is kinda sad and makes require.js overkill for such a small task.
What I want to make is extend $inject service so that it would know how to load external files and that's how imagine it should look like:
Create di.config.js file where I specify paths for various "injectables" (much like require.conf.js
Create a simple script loader that would know how to recursively load modules
Now for DI mechanism
--
if (injectableIsLoaded) {
angular.module('app').controller('Controller', function (dep1) {});
} else if (injectableHasPath) {
diLoader.load(injectablePath).then(evalModule);
} else {
throw "No path for module";
}
Does anyone know if that would be possible at all?
What I win from this - I don't need to wrap my angular code in weird require.js calls, don't need a build step, can keep all files separete and load them separatly on demand...
I was trying to create a script that would include all the required ones to make my Angular app work but doesn't seem to work:
function include(destination) {
var e = window.document.createElement('script');
e.setAttribute('src', destination);
window.document.body.appendChild(e);
}
include('../Scripts/myApp/main.js');
include('../Scripts/myApp/appConfig.js');
include('../Scripts/myApp/MyAppController.js');
include('../Scripts/myApp/Service1.js');
include('../Scripts/myApp/Service2.js');
Unfortunately, if I try to include the app scripts like that (it works btw, they are loaded into the page perfectly), I get an error as angular detects my "ng-app='myAppName'" directive but finds no module declared for it (I suppose because the script hasn't been loaded yet when angular checks). If I then try to at least place the module instantiation on a script called directly by the HTML page (the main.js) then it complains for the controller, services, etc. not existing and if I attach those to the app inside their own files (app.controller(), app.factory, ...) it does not complain but it doesn't happen either.
Looks like loading the scripts this way makes Angular not execute them as they may be loaded too late or something. The only way of doing it has been to set a script tag calling the scripts on the HTML.
But I have the feeling that I'm missing something, that there is a way of doing this and I just don't see it.
Does anyone know how to load all the required scripts by just calling one of them? Is there something on the angular app config that can be set to do so maybe?
It just looks too dirty to make so many calls and also bad in terms of performance.
I've checked that there is something called Yeoman but it looks like too big for what I want, then I saw RequireJS but I'm unsure that's what I really want and again not sure I want to install another library just for this. Finally, I've seen that on the app config you can set where to find the views and even a way to load the controller (I think, I'm unsure of anything now my head is so dizzy) so I was thinking that maybe Angular has already something to do this which looks so natural, joining the scripts into one?
Many thanks.
This is because your scripts are being loaded in asynchronously and angular is compiling the document synchronously as the page is loaded (since I'm assumnig angular.js is on the script includes as I don't see it above). You will have to manually bootstrap the app in this scenario, as you would also have to do when using RequireJS. Here is an example... Since you are not using a library like RequireJS, I am also putting a hacky counter to see when all your scripts are loaded to callback and bootstrap angular when all scripts have loaded:
function include(destination) {
var e = window.document.createElement('script');
e.setAttribute('src', destination);
//IE detect when script has fully loaded... Increase number of loaded scripts and
//call bootstrap if all have loaded.
e.onreadystatechange = function () {
if (newjs.readyState === "loaded" || newjs.readyState === "complete") {
e.onreadystatechange = null;
loadedScripts++;
}
if (loadedScripts === numScripts) {
bootstrapAngular("myAppName");
}
}
//Other browsers. Same logic as above - Increase scripts loaded and call bootstrap when complete
e.onload = function () {
loadedScripts++;
if (loadedScripts === numScripts) {
bootstrapAngular("myAppName");
}
}
window.document.body.appendChild(e);
}
//5 scripts being loaded
var numScripts = 5;
//0 currently loaded
var loadedScripts = 0;
include('../Scripts/myApp/main.js');
include('../Scripts/myApp/appConfig.js');
include('../Scripts/myApp/MyAppController.js');
include('../Scripts/myApp/Service1.js');
include('../Scripts/myApp/Service2.js');
function bootstrapAngular(appName) {
//Assuming you are putting ng-app at document level... Change to be your specific element.
angular.bootstrap(document, [appName]);
}
The solution above is a little hacky as we are keeping track of all the scripts that have loaded. This is where a library like RequireJS would play very nicely because it would call your callback function when the scripts you want have been loaded. An example would be:
require(['angular', 'jquery', 'appConfig', 'MyAppController', 'Service1', 'Service2'], function (ng, $) {
//bootstrap when all is ready
$(function () {
ng.bootstrap(document, ["myAppName"]);
});
});
If you're using AngularJS (or any similarly advanced framework) then it's definitely time for you to learn about module loading. Injecting <script> tags quickly becomes untenable as you start to build more modules and start dealing with more complex dependency chains.
RequireJS is a great package and can do everything under the sun. Here's a tutorial on using the two together that looks pretty good. Browserify is a similar library that lets you manage dependencies with NPM. It's a little bit simpler than RequireJS, which might be a plus for you. Here's a tutorial that looks decent.
Both RequireJS and Browserify come with command-line build tools, which are great because they can combine all of your source code into a single file when you're ready to deploy your app to a server.
I should warn you, however, that this stuff isn't easy. There's a fairly steep learning curve. In all likelihood, though, this is stuff you're going to need to learn sooner or later anyway.
I my webapp I use about 30 js files, each file containing 1 function. All these function are now selfinvoking and have references to each other.
The problem with this is that the order of scripts in the index.jsp matters. If a method is called on a function which has not been invoked yet we get a undefined error.
For a while we could overcome this by controlling the order of the <script> tags, but I would like to do this by using a loader script.
I have set up a small fiddle to show my concept. My biggest concern is that I have to declare my objects globally, in order to have them be accessible in the jquery(document).ready() function.
Is this an OK pattern? Any hints highly appreciated!
You could use RequireJS or similar loader, which would handle script dependencies for you.
You would need to modify each of JS file to make it a module in a similar fashion to this example:
// File: module3.js
define(["module1", "module2"], function(m1, m2) {
// Here, module1 and module2 are guaranteed to be loaded.
});
Then, you would make one "main" script (I usually call it main.js) and require several modules:
require(["module3"], function (m3) {
// Here module3 is loaded, as well as module1 and module2
// - because module3 depends on them.
});
And put this in your HTML:
<script data-main="scripts/main" src="scripts/require.js"></script>
Try to build your server-side architecture to serve proper js files (and other static files) per page. Create 1 minified js file for page and initialize objects scope in html files.
I'm using RequireJS in my app, but don't quite well understand all aspects of it's work.
I have main.js file, where dependencies are described. I have Backbone.Router component up and running, which triggers different application classes (class which is responsible to create main view). Some code you can see here.
What I can see with requireJS: even if some view has not been yet 'required' (mean explicitly call to require('./subviews/view)), it's still loaded and inside it loads all templates (I use requireJS text plugin). If I'm adding new application but it's subviews are not ready, but I never used the application - nonexisting subviews are still loaded and I'm getting 404 errors.
Not sure I explained everything clearly, but hope you got point.
Looks like you are using the CommonJS sugared form of define(). Note that this is just a convenience for wrapping Node/CommonJS code, but AMD modules do not operate like Node modules.
An AMD loader will scan for require('') calls in the factory function of the module, and make sure to load all the modules. Otherwise, synchronous access to the that module, by doing a require('./apps/DashboardApp');, would fail in the browser, since file IO is by default async network IO.
If you want to delay loading of some scripts, then you need to use the callback-form of require:
require(['./apps/DashboardApp'], function (DashboardApp) {
});
but this call is an async call, so you would have to adjust your module's public API accordingly.
So basically, if you want to do on-demand loading of a dependency, the callback form of require is needed, given the async nature of file IO in the browser.
Because RequireJS loads all required dependencies. By taking quick look at your code I see that you load routing module and routing has:
var ViewManager = require('ViewManager');
That means it will load ViewManager, dependecies that are specified by ViewManager and other dependencies that those modules need. Essentially when you include require(...), it's the same as specifying dependency. This will be transformed by RequireJS into
define(['ViewManager'], ...)
I'm building a complex web app and to keep things simple, I made various modules (objects) in various files. Those modules can be required on some page, and not on others.
For that reason, I'd like to avoid loading all the modules for any pages, increasing the amount of useless requests.
So far, I work like this :
I include all needed libraries
After that, I instantiate these librairies in jQuery(function() {}); with specifics #ids or .classes arguments for that current page
Everything works fine, but since my app is growing beyond easy, I'd like to manage my JS with RequireJS.
And that's where things start to be a little confusing for me.
I know I can load my module when required, using require(['module1', 'module2', 'etc'], function (module1, module2, etc) {}), but how can I say :
"on this page, you load these modules, and instantiate them with those #ids and .classes"
and
"on this other page, you load only that module, with this #other-id"
?
module1 will, for example, load data from the api, and list them to a specific table given as parameter :
// in page 1
var mod1 = new Module1($('#content>table');
mod1.init(); // will load the data from the api with the url located at <table data-api-url="http://....">
// in page 2
var mod1 = new Module1($('#content .items>table'); // The table where we want the data to be populated is not at the same position!
mod1.init();
That means, depending on the page, I'll have to load my modules differently. That's how I don't know how to do using RequireJs :/
What you need is a javascript file for each page. That file will be responsible to execute your page-specific code.
I'll assume that you will use r.js to optmize and pack your code.
We can interpret Module1, Module2 etc. as libraries, because they will be used on multiple pages. To avoid browser do one request for each library module you can include this modules on your optimized main file:
Configure the "modules:" attribute of your build profile like this:
...
modules: [
{
name: "main" // this is your main file
includes: [
"module1",
"module2",
"etc..."
]
}
]
...
By doing this you tell to requirejs something like this: Optimize my "main.js" file and include in it all its dependencies, also includes the "module1", "module2" etc.
You have to do this because on your main file you do not include these modules on the require()/define() call, but you still want they available in case the page-specific module needs them. You don't have to do this for every library module you have, just for those that will be used by most of your pages.
Then, you create a javascript file for your page to use those modules:
define(function(require, exports, module) {
var $ = require("jquery"),
module1 = require("module1");
var mod1 = new Module1($('#content>table'));
mod1.init();
//other page specific-code.
});
And then on the html file:
<script data-main="main" data-start="home" src="require.js"></script>
So, when page loads, it will make a request for require.js, then another for main.js and then another for home.js and that's all.
I'll put the link for the another question so this answer get some context: How to use RequireJS build profile + r.js in a multi-page project
The creator of RequireJS actually made an example project that uses page-specific main files: https://github.com/requirejs/example-multipage