RequireJS include dependency only in build - javascript

I'm using html2js plugin to compile my Angular templates ( this is not really relevant but wanted to explain ).
It builds a templates.js during the build process that requires angular to be in scope like:
angular.module("views/date.html", ...... ");
so essentially I need this file to be in the main.js dependency chain. Now I was able to get it to be included in the build doing:
deps: ['templates']
in my build.config for require but its never 'required' by any modules so it never executes.
Bottom line: In require, how can I require a file at the top of the dependency chain only at build time?

Ended up setting a variable in the html page if in dev mode like:
<script type="text/javascript">window.dev = true;</script>
then made a require statement like:
if(!window.dev){
require(['templates', ... ]);
else
// don't include templates

Related

Old AngularJS project migration to bundled js + minification with browserify

I have a very old AngularJS project which is quite big. Instead of creating a bundled .js file composed of all the required code, this project is organized in the following way:
All the .js files are directly loaded in index.html with a <script src="path/to/js">
Even the dependencies minified .js files are loaded in the same way, examples:
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/angular-resource/angular-resource.min.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.min.js"></script>
The code makes vast use of definitions (functions, classes, enums and so on) declared in different .js files without importing them (because they are all available globally). Examples:
// inside one file, PastFuture is not declared here
if (self.pastFuture === PastFuture.FUTURE) {
... // do something
}
// inside another file, it is not exported or anything, it is just defined
const PastFuture = {
PAST: 'PAST',
FUTURE: 'FUTURE'
};
I want to migrate this project into something a bit more "standard". I've removed bower for npm dependencies but now I'm stuck at forcing all these .js files to get bundled together.
The major problem of the code is that once bundled with browserify, all the definitions not imported stops working, for example, PastFuture is not defined unless I import it manually wherever is required.
Is there a way to overcame / solve this problem without adding exports and require in all the files of the project?
I was thinking that if I concatenate all those .js files instead of trying to make them require each other, it would have the safe effect without the need to add exports and imports.. but as a solution, it just sounds like a hack to me and I was searching for something more correct.

'define' is not defined error on RequireJS & Webapp Yo generator

I have struggled a few days to figure this out,, but finally I need your help today.
my repo: https://github.com/seoyoochan/bitsnut-web
what I want to achieve:
- Load and optimize r.js
- Write bower tasks for RequireJS and r.js :
tasks are: minify & uglify & concatenation for RequireJS, and optimise with r.js on production
- How to exclude js script tags in index.html when using wiredep tasks and load them through RequireJS loader?
I use Yeoman 'Webapp' generator and generated the scaffold app.
I installed backbone, marionette, text, underscore, and etc via bower install
I modified bower.json by removing dependencies and left only "requirejs": "~2.1.16" on dependencies. (devDependencies is empty)
because I use [2][grunt-wiredep], everything is automatically loaded bower_components into index.html.
I modified .bowerrc to store dependencies at app/scripts/vendor.
However, the problem is that I don't know how to successfully load them through ReuqireJS and not to load the vendors as script tags inside index.html.
I have to write some tasks for RequireJS and r.js, but don't know how to achieve this goal ( I installed grunt-contrib-requirejs though )
I want to follow the 4th method to make use of r.js at https://github.com/jrburke/requirejs/wiki/Patterns-for-separating-config-from-the-main-module. but the issue I encountered was that RequireJS's documentation does suggest to not use named module, but anonymous module.
I would like to hear various opinions about how I should approach.
I really appreciate your help in advance!
You load your scripts manually here and here, rendering the whole point of requireJS useless. You also load main first here config.js#L49.
Instead, you should only have this line in your index.html
<script data-main="scripts/config" src="scripts/vendor/requirejs/require.js"></script>
And load all your dependencies in that file (like you do with main) using define() and require(). As you have set exports, which sets the values as globals, the functions can be empty. Here's an sample:
define([
"jquery",
"underscore",
"backbone",
"marionette",
"modernizr"
], function () {
require([
"backbone.babysitter",
"backbone.wreqr",
"text",
"semantic"
], function () {
/* plugins ready */
});
define(["main"], function (App) {
App.start();
});
});
Also the baseUrl is the same as the directory as your data-main attributes folder (http://requirejs.org/docs/api.html#jsfiles):
RequireJS loads all code relative to a baseUrl. The baseUrl is
normally set to the same directory as the script used in a data-main
attribute for the top level script to load for a page. The data-main
attribute is a special attribute that require.js will check to start
script loading.
So I think your baseUrl in config.js points to scripts/scripts/-folder which doesn't exist. It could/should be vendor/ instead (and remove the vendor part from all of the declarations) or just left empty.
Instead of wiredep, you could try using https://github.com/yeoman/grunt-bower-requirejs which does similar things to wiredep but specially for bower/requirejs apps (see: https://github.com/stephenplusplus/grunt-wiredep/issues/7)
Your repo doens't include the dist-folder for jQuery, but otherwise here's a working sample of config.js: http://jsfiddle.net/petetnt/z6Levh6r/
As for the module-definition, it should be
require(["dependency1", "dependency2"])
and the module should return itself. Currently your main file sets itself as a dependency
require(["main", "backbone", "marionette"], function(App, Backbone, Marionette){
As you already set the backbone and marionette as globals with exports, you can again set the function attributes empty, so your main file should look like this:
require(["backbone", "marionette"], function(){
"use strict";
var App = new Backbone.Marionette.Application();
App.addInitializer(function(){
console.log("hello world!");
Backbone.history.start();
});
return App;
});
And as you already use define to load main, don't require it again. Instead just call App.start() inside the define function.
https://jsfiddle.net/66brptd2/ (config.js)

Is it possible to exclude a module from a require optimizer build config but have that module and it's dependencies be optimized separately?

So the situation is the following:
I have a bunch of page specific js files which I'm optimizing using r.js.
99% of them define a a module called core.js as a dependency. Core has 5 dependencies of it's own.
I wanted to leverage caching by excluding core from the optimised versions of the page js files.
So in my build.js I tried something along the lines of:
modules: [
{
name : 'modules/core'
},
{
name: 'homepage',
exclude : ['modules/core']
}
]
When I run the optimiser it optimises both modules/core & homepage just fine.
Going to a page that uses homepage.js the issue is that: core.js & it's dependencies are being loaded in individually, leading to 7 requests instead of 2.
In the above example, what I'm trying to achieve is: have homepage.js and it's dependencies optimised into a single file and have it load in an optimised version of core.js rather then loading in the core.js and it's dependencies separately.
Is that even possible?
You have two options, after a build either:
1) modify the top level loading such that modules/core is loaded before any other loading:
require(['modules/core'], function () {
//Now do normal require stuff in here
});
Use another nested require() call in there for homepage if you see its modules requested before homepage finishes loading.
2) Insert a require.config block that points all the modules in core to the core file. requirejs will only fetch the core.js file once when multiple module IDs all point to it:
require.config({
paths: {
'mod/one': 'modules/core',
'mod/two', 'modules/core',
...
}
});
Or see this kind of example project that sets up loading a common and then a page-specific layer, but works without having to do a source modification after a build (just uses a variation of #1, but sets it up to work in source form):
https://github.com/requirejs/example-multipage

How to use RequireJS build profile + r.js in a multi-page project

I am currently learning RequireJS fundamentals and have some questions regarding a build profile, main files, and use of RequireJS with multi-page projects.
My project's directory structure is as follows:
httpdocs_siteroot/
app/
php files...
media/
css/
css files...
js/
libs/
jquery.js
require.js
mustache.js
mains/
main.page1.js
main.page2.js
main.page3.js
plugins/
jquery.plugin1.js
jquery.plugin2.js
jquery.plugin3.js
utils/
util1.js
util2.js
images/
Since this project is not a single-page app, I have a separate main file for each page (although some pages use the same main file).
My questions are:
Is RequireJS even practical for projects that are not single-page?
Without using the optimizer, each of my main files start with essentially the same config options:
requirejs.config({
paths: {
'jquery': 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min'
},
baseUrl: '/media/js/',
// etc...
});
require(['deps'], function() { /* main code */ });
Is there a way to avoid this? Like having each main file include the same build profile without having to actually build it?
Should r.js go in httpdocs_siteroot's parent directory?
Is there something glaringly wrong with my app dir structure or my use of RequireJS?
First of all, this is not a question with a unique solution. I'll explain the way I use RequireJS that works for me, and may work for you :)
Second, English is not my mother language. Corrections and tips about the language will be very appreciated. Feel free, guys :)
1) Is require js even practical for projects that are not single-page?
It depends. If your project does not have shared code between pages for example, RequireJS help will be modest. The main idea of RequireJS is modularize the application into chunks of reusable code. If your application uses only page-specific code, then using RequireJS may not be a good idea.
2) Without using the optimizer, each of my main files start with essentially the same config options. Is there a way to avoid this? Like having each main file include the same build profile without having to actually build it?
The only way I see is making the configuration on the main file, or create a module that will configure RequireJS and then use that module as the first dependency on main.js. But this can be tricky. I do not use many main.js files in my applications; I use only one that acts as a loader (see below).
3) Should r.js go in httpdocs_siteroot's parent directory?
Not necessarily. You can put it inside the /media directory, since all your client stuff is there.
4) Is there something glaringly wrong with my app dir structure or my use of requirejs?
I would not say that. On the other hand, the structure is perhaps a bit too fragmented. For example, you can put all '3rd party stuff' inside a /vendor directory. But this is just sugar; your structure will work well and seems right. I think the major problem is the requirejs.config() call in multiple main files.
I had the same problems you are having now and I ended up with the following solution:
1) Do not wrap the non-AMD-compliant files with a define. Although it works, you can achieve the same results using the "shim" property in requirejs.config (see below).
2) In a multi-page application, the solution for me is not to require the page-specific modules from the optimized main.js file. Instead, I require all the shared code (3rd party and my own) from the main file, leaving the page-specific code to load on each page. The main file ends up only being a loader that starts the page-specific code after loading all shared/lib files.
This is the boilerplate I use to build a multi-page application with requirejs
Directory structure:
/src - I put all the client stuff inside a src directory, so I can run the optimizer inside this directory (this is your media directory).
/src/vendor - Here I place all 3rd party files and plugins, including require.js.
/src/lib - Here I place all my own code that is shared by the entire application or by some pages. In other words, modules that are not page-specific.
/src/page-module-xx - And then, I create one directory for each page that I have. This is not a strict rule.
/src/main.js: This is the only main file for the entire application. It will:
configure RequireJS, including shims
load shared libraries/modules
load the page-specific main module
This is an example of a requirejs.config call:
requirejs.config({
baseUrl: ".",
paths: {
// libraries path
"json": "vendor/json2",
"jquery": "vendor/jquery",
"somejqueryplugion": "vendor/jquery.somejqueryplufin",
"hogan": "vendor/hogan",
// require plugins
"templ": "vendor/require.hogan",
"text": "vendor/require.text"
},
// The shim section allows you to specify
// dependencies between non AMD compliant files.
// For example, "somejqueryplugin" must be loaded after "jquery".
// The 'exports' attribute tells RequireJS what global variable
// it must assign as the module value for each shim.
// For example: By using the configutation below for jquery,
// when you request the "jquery" module, RequireJS will
// give the value of global "$" (this value will be cached, so it is
// ok to modify/delete the global '$' after all plugins are loaded.
shim: {
"jquery": { exports: "$" },
"util": { exports: "_" },
"json": { exports: "JSON" },
"somejqueryplugin": { exports: "$", deps: ["jquery"] }
}
});
And then, after configuration we can make the first require() request
for all those libraries and after that do the request for our "page main" module.
//libs
require([
"templ", //require plugins
"text",
"json", //3rd libraries
"jquery",
"hogan",
"lib/util" // app lib modules
],
function () {
var $ = require("jquery"),
// the start module is defined on the same script tag of data-main.
// example: <script data-main="main.js" data-start="pagemodule/main" src="vendor/require.js"/>
startModuleName = $("script[data-main][data-start]").attr("data-start");
if (startModuleName) {
require([startModuleName], function (startModule) {
$(function(){
var fn = $.isFunction(startModule) ? startModule : startModule.init;
if (fn) { fn(); }
});
});
}
});
As you can see in the body of the require() above, we're expecting another attribute on the require.js script tag. The data-start attribute will hold the name of the module for the current page.
Thus, on the HTML page we must add this extra attribute:
<script data-main="main" data-start="pagemodule/main" src="vendor/require.js"></script>
By doing this, we will end up with an optimized main.js that contains all the files in "/vendor" and "/lib" directories (the shared resources), but not the page-specific scripts/modules, as they are not hard-coded in the main.js as dependencies. The page-specific modules will be loaded separately on each page of the application.
The "page main" module should return a function() that will be executed by the "app main" above.
define(function(require, exports, module) {
var util = require("lib/util");
return function() {
console.log("initializing page xyz module");
};
});
EDIT
Here is example of how you can use build profile to optimize the page-specific modules that have more than one file.
For example, let's say we have the following page module:
/page1/main.js
/page1/dep1.js
/page1/dep2.js
If we do not optimize this module, then the browser will make 3 requests, one for each script.
We can avoid this by instructing r.js to create a package and include these 3 files.
On the "modules" attribute of the build profile:
...
"modules": [
{
name: "main" // this is our main file
},
{
// create a module for page1/main and include in it
// all its dependencies (dep1, dep2...)
name: "page1/main",
// excluding any dependency that is already included on main module
// i.e. all our shared stuff, like jquery and plugins should not
// be included in this module again.
exclude: ["main"]
}
]
By doing this, we create another per-page main file with all its dependencies. But, since we already have a main file that will load all our shared stuff, we don't need to include them again in page1/main module.
The config is a little verbose since you have to do this for each page module where you have more than one script file.
I uploaded the code of the boilerplate in GitHub: https://github.com/mdezem/MultiPageAppBoilerplate.
It is a working boilerplate, just install node and r.js module for node and execute build.cmd (inside the /build directory, otherwise it will fail because it uses relative paths)
I hope I have been clear. Let me know if something sounds strange ;)
Regards!
<script data-main="js/main" src="js/lib/require.js"></script>
// file: js/main
require(['./global.config'], function(){
require(['./view/home'], function() {
// do something
});
});
This is what I used in my project.

Can't find haml-js templates during Jasmine test run

I'm trying to test some backbone.js views using Jasmine (via jasmine-headless-webkit). Everything is working well, except that my haml-js templates aren't accessible under test.
The following code in my view works fine:
render: =>
html = JST['views/avia_view_template']()
$(#el).html(html)
... but when it's run as part of a Jasmine spec I get the following failure:
ReferenceError: Can't find variable: JST in /home/duncan/avia/app/assets/javascripts/views/avia_view.js.coffee
I suspect I'm doing something wrong in jasmine.yml. I've explicitly included the template file it still fails:
src_files:
- "vendor/**/*.{js,coffee}"
- "lib/**/*.{js,coffee}"
- app/assets/javascripts/application.js
- app/assets/javascripts/avia.js
- app/assets/javascripts/jquery-1.6.4.js
- app/assets/javascripts/underscore.js
- app/assets/javascripts/backbone.js
- app/assets/javascripts/jquery.jqGrid.min.js
- app/assets/javascripts/views/avia_view_template.jst.hamljs
- app/assets/javascripts/views/avia_view.js.coffee
Perhaps I'm just taking the wrong approach here ... should I be using Jasmine to stub & mock out the calls to JST and jQuery? A strictly unit-testing approach says I should, in which case the lack of template access is a non-issue.
Any tips - either on my approach to testing, or the specific JST failure, would be greatly appreciated.
No need to stub, you just need to set up your asset paths correctly. In order to take advantage of the Sprockets integration in 0.8.0 and above, the best way to set up your jasmine.yml file would be like this:
src_dir: app/assets/javascripts
asset_paths:
- lib/assets/javascripts
src_files:
- "**/*"
This will set up Sprockets to look in app/assets/javascripts and lib/assets/javascripts, and will tell jasmine-headless-webkit to pull every possible file in both directories. Jasmine's normal file requiring wouldn't be used in this case, just Sprockets.
Then, set up your require statements like you would normally in your JS files. So in 'application.js.coffee':
#= require jquery-1.6.4
#= require avia
#= require underscore
#= require backbone
#= require jquery.jqGrid.min
#= require_tree .
alert "Look, Internet codes!"
And in avia_view.js.coffee:
#= require views/avia_view_template.jst.hamljs
class window.AviaView extends Backbone.View
template: JST['views/avia_view_template']
... code ...
Of course, those .hamljs templates won't get loaded unless a Sprockets-capable Haml processor has been loaded. So you would want to have a Gemfile that had at least this in it:
gem 'jasmine-headless-webkit'
gem 'haml-sprockets'
# also bring in backbone and jquery
gem 'jquery-rails'
gem 'backbone-rails'
Then, if your application itself knows what to do when those vendored JS gems are loaded, you could get rid of you own copies of jQuery and Backbone, and you'd also have .hamljs templates available. At that point, you should run using Bundler:
bundle exec jasmine-headless-webkit
Last thing, the best way to make sure everything's actually getting loaded is to use the list option:
bundle exec jasmine-headless-webkit -l
That will run everything through JHW's and Sprockets' file loaders and print out the order of files to be included. This will help diagnose require problems, since you always have to deal with both Jasmine-style and Sprockets-style loading in a single scenario. Setting up your src files to be loaded entirely via Sprockets simplifies the process a lot, so I would recommend a setup a lot like this one.

Categories

Resources