How to properly build an AMD app as a single file with r.js using grunt? - gruntjs

I keep seeing this error when executing the compiled file:
Uncaught Error: No json
Here's my current requirejs grunt task configuration:
requirejs: {
options: {
baseUrl: "build/repos/staging/dev",
mainConfigFile: "dev/main.js",
generateSourceMaps: false,
preserveLicenseComments: false,
name: "almond",
out: "./static/js/compiled.js",
//excludeShallow: ['vendor'],
findNestedDependencies: true,
removeCombined: true,
//wrap: true,
optimize: "uglify2",
uglify2: {
output: {
beautify: true,
},
lint: true,
mangle: false,
compress: false,
compress: {
sequences: false
}
}
}
}
And here's my dev/main.js file:
// This is the runtime configuration file.
// It also complements the Gruntfile.js by supplementing shared properties.require.config({
waitSeconds: 180,
urlArgs: 'bust=' + (new Date()).getTime(),
paths: {
"underscore": "../vendor/underscore/underscore",
"backbone": "../vendor/backbone/backbone",
"layoutmanager": "../vendor/layoutmanager/backbone.layoutmanager",
"lodash": "../vendor/lodash/lodash",
"ldsh": "../vendor/lodash-template-loader/loader",
"text": "../vendor/requirejs-plugins/lib/text",
"json": "../vendor/requirejs-plugins/json",
"almond": "../vendor/almond/almond",
// jquery
"jquery": "../vendor/jquery/jquery",
"jquery.transit": "../vendor/jquery.transit/jquery.transit",
"jquery.mousewheel": "../vendor/jquery.mousewheel/jquery.mousewheel",
"jquery.jscrollpane": "../vendor/jquery.jscrollpane/jquery.jscrollpane"
},
shim: {
'backbone': {
deps: ['underscore']
},
'layoutmanager': {
deps: ['backbone', 'lodash', 'ldsh']
},
'jquery.transit': {
deps: ['jquery']
},
'json': {
deps: ['text']
}
}});
// App initialization
require(["app"], function(instance) {
"use strict";
window.app = instance;
app.load();
});
And finally, my dev/app.js file:
define(function(require, exports, module) {
"use strict";
// External global dependencies.
var _ = require("underscore"),
$ = require("jquery"),
Transit = require('jquery.transit'),
Backbone = require("backbone"),
Layout = require("layoutmanager");
module.exports = {
'layout': null,
'load': function() {
var paths = [
// ***
// *** 1- define its path
// ***
'json!config/main.json',
'modules/nav',
'modules/store',
'modules/utils',
'modules/preloader',
'modules/popup',
'modules/login',
'modules/user',
'modules/footer',
];
try {
require(paths, function(
// ***
// *** 2- call it a name
// ***
Config,
Nav,
Store,
Utils,
Preloader,
Popup,
Login,
User,
Footer
) {
// ***
// *** 3- instance it in the app
// ***
app.Config = Config;
app.Nav = Nav;
app.Store = Store;
app.Utils = Utils;
app.Preloader = Preloader;
app.Popup = Popup;
app.Login = Login;
app.User = User;
app.Footer = Footer;
// require and instance the router
require(['router'], function(Router) {
// app configuration
app.configure();
// app initialization
app.Router = new Router();
});
});
} catch (e) {
console.error(e);
}
},
'configure': function() {
var that = this;
// set environment
this.Config.env = 'local';
// Ajax global settings
Backbone.$.ajaxSetup({
'url': that.Config.envs[that.Config.env].core,
'timeout': 90000,
'beforeSend': function() {
},
'complete': function(xhr, textstatus) {
}
});
// Template & layout
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
Layout.configure({
// Allow LayoutManager to augment Backbone.View.prototype.
manage: true,
// Indicate where templates are stored.
prefix: "app/templates/",
// This custom fetch method will load pre-compiled templates or fetch them
// remotely with AJAX.
fetch: function(path) {
// Concatenate the file extension.
path = path + ".html";
// If cached, use the compiled template.
if (window.JST && window.JST[path]) {
return window.JST[path];
}
// Put fetch into `async-mode`.
var done = this.async();
// Seek out the template asynchronously.
$.get('/' + path, function(contents) {
window.JST[path] = contents;
done(_.template(contents));
}, "text");
}
});
},
};
});
Any ideas why is that json module not "required" when executing grunt requirejs ?
Thanks in advance.

Not sure if this is still an issue, but from the requirejs optimizer docs (http://requirejs.org/docs/optimization.html):
The optimizer will only combine modules that are specified in arrays of string literals that are passed to top-level require and define calls, or the require('name') string literal calls in a simplified CommonJS wrapping. So, it will not find modules that are loaded via a variable name...
It sounds like the requirejs optimizer doesn't like the require calls being made with a variable that is an array of dependencies.
It also sounds like the requirejs optimizer doesn't like the syntax of require([dependency array], callback) being used within the actual file being optimized.
You may have to refactor your dependency declarations within dev/app.js to conform to this specification. For example, you might be able to use the following refactoring of steps 1 and 2:
var Config = require('json!config/main.json');
var Nav = require('modules/nav');
var Store = require('modules/store');
var Utils = require('modules/utils');
var Preloader = require('modules/preloader');
var Popup = require('modules/popup');
var Login = require('modules/login');
var User = require('modules/user');
var Footer = require('modules/footer');
If this does work, it looks like you'll also have to do something similar for the Router dependency declaration.
Also, a minor addition that you might want to include to your requirejs configuration once you get it running is:
stubModules : ['json']
Since the built file should have the JSON object within it, you won't even need the plugin within the built file! As such, you can reduce your file size by removing the json plugin from it.

Related

Meteor methods cannot be added during run time when using typescript with SystemJs

I used meteor-typescript-compiler (https://github.com/meteor-typescript/meteor-typescript-compiler) for my new project. After setting up the project and getting the great help from #basarat, I was able to let the project startup normally. However, look like the generated js file is not triggered during the time server is executed, thus all the Meteor methods are not triggered and added to Meteor.
/// <reference path="../typings/definitions/meteor.d.ts" />
export class App {
constructor() {
}
}
Meteor.startup(function() {
console.log ('added to stack');
});
Meteor.methods({
'test': function() {
console.log('from new 2');
}
});
When starting up the meteor server, the generated js file is
(function(){
/////////////////////////////////////////////////////////////////////////
// //
// server/main.js //
// //
/////////////////////////////////////////////////////////////////////////
//
/// <reference path="../typings/definitions/meteor.d.ts" /> // 1
System.register("server/main", [], function(exports_1) { //
var App; //
return { //
setters:[], //
execute: function() { //
App = (function () { //
function App() { //
} //
return App; //
})(); //
exports_1("App", App); //
Meteor.startup(function () { //
console.log('added to stack'); //
}); //
Meteor.methods({ //
'test': function () { //
console.log('from new 2'); //
} //
}); //
} //
} //
}); //
//# sourceMappingURL=main.js.map //
/////////////////////////////////////////////////////////////////////////
}).call(this);
//# sourceMappingURL=main.js.map
And this is my tsconfig.json (I also added the .tsconfig to the root folder based on the meteor-typescript-compiler 's guidance)
{
"compilerOptions": {
"module": "commonjs",
"sourceMap": true,
"isolatedModules": false,
"noImplicitAny": true
},
"exclude": [
"typings"
]
}
The line added to stack is not shown when server is running (I added package systemjs:systemjs into meteor). For the testing purpose, I uploaded the project into https://github.com/bubuzzz/new1.git
thus all the Meteor methods are not triggered and added to Meteor.
You need to load the main.js file as a dependency. i.e someone needs to call the function that is registered against server/main (in System.register("server/main", [], function(exports_1) {)
Reading the docs : https://github.com/systemjs/systemjs#browser I seems you need
System.import('server/main.js');

How to dynamically set a grunt <%= var %>

How do I change the value of a grunt variable (<%= var %>)
before running a task?
I have the following code, but it does not seem to process the json and exchange the placeholders(<%= var %>) with the new value.
module.exports = function(grunt) {
var modules = ['a','b','c'];
grunt.initConfig({
module: module,
som_task: {
blah: {
files: 'test/<%= module %>',['<%= module %>']
}
}
});
grunt.registerTask('release-all', 'abc', function() {
for (var module in modules) {
grunt.task.run(['setModule:'+module,'release']);
}
});
grunt.registerTask('setModule', 'change the variable in the ', function(module) {
grunt.config('module',module);
});
}
I found my answer. I created a function to return the config object with a different variable and then re-init it before running my tasks.
function getConfig(value) {
return {
"value":value,
...
};
}
grunt.registerTask('change', 'Swap Value', function(passValue) {
grunt.initConfig(getConfig(passValue));
});
//set the default
grunt.initConfig(getConfig("initialValue"));
//run the task and pass in the new value before running your other task
runt.task.run(['change:newValue']);

Location of Intern reporters output files like corbertura or html report

I'm using Grunt with Intern and set some reporters to lcovhtml and cobertura:
grunt.initConfig({
intern: {
runner: {
options: {
config: 'tests/intern',
runType: 'runner',
reporters: ['pretty', 'lcovhtml','junit','cobertura']
}
}
},
Is there any configuration to control output directory of these files for all or each reporter?
For example, by adding a parameters reportDir to the options object defined in your Gruntfile.js, you can update intern/lib/reporters/lcovhtml.js with:
define([
'dojo/node!istanbul/lib/collector',
'dojo/node!istanbul/lib/report/html',
'dojo/node!istanbul/index'
], function (Collector, Reporter) {
var collector = new Collector(),
reporter = new Reporter();
//...
});
with:
define([
'../args',
'dojo/node!istanbul/lib/collector',
'dojo/node!istanbul/lib/report/html',
'dojo/node!istanbul/index'
], function (args, Collector, Reporter) {
var collector = new Collector(),
reporter = new Reporter({ dir: args.reportDir });
//...
});
You can propagate a similar update in cobertura.js and junit.js reporters.
Note: I documented this approach in https://github.com/theintern/intern/issues/71. The patch for the corresponding issue has not yet been published (pushed to Intern 2.3).

Browserify - multiple entry points

I am using Browserify within gulp. I am trying to compile down my tests to a single file as well. But unlike my main app, which I have working just fine, I am having trouble getting the tests to compile. The major difference is the tests have multiple entry points, there isn't one single entry point like that app. But I am getting errors fro Browserify that it can't find the entry point.
browserify = require 'browserify'
gulp = require 'gulp'
source = require 'vinyl-source-stream'
gulp.task 'tests', ->
browserify
entries: ['./app/js/**/*Spec.coffee']
extensions: ['.coffee']
.bundle
debug: true
.pipe source('specs.js')
.pipe gulp.dest('./specs/')
Below is a task I was able to build that seems to solve the problem. Basically I use an outside library to gather the files names as an array. And then pass that array as the entry points
'use strict;'
var config = require('../config');
var gulp = require('gulp');
var plumber = require('gulp-plumber');
var glob = require('glob');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
gulp.task('tests', function(){
var testFiles = glob.sync('./spec/**/*.js');
return browserify({
entries: testFiles,
extensions: ['.jsx']
})
.bundle({debug: true})
.pipe(source('app.js'))
.pipe(plumber())
.pipe(gulp.dest(config.dest.development));
});
Here's an alternate recipe that fits more with the gulp paradigm using gulp.src()
var gulp = require('gulp');
var browserify = require('browserify');
var transform = require('vinyl-transform');
var concat = require('gulp-concat');
gulp.task('browserify', function () {
// use `vinyl-transform` to wrap around the regular ReadableStream returned by b.bundle();
// so that we can use it down a vinyl pipeline as a vinyl file object.
// `vinyl-transform` takes care of creating both streaming and buffered vinyl file objects.
var browserified = transform(function(filename) {
var b = browserify(filename, {
debug: true,
extensions: ['.coffee']
});
// you can now further configure/manipulate your bundle
// you can perform transforms, for e.g.: 'coffeeify'
// b.transform('coffeeify');
// or even use browserify plugins, for e.g. 'minifyiy'
// b.plugins('minifyify');
// consult browserify documentation at: https://github.com/substack/node-browserify#methods for more available APIs
return b.bundle();
});
return gulp.src(['./app/js/**/*Spec.coffee'])
.pipe(browserified)/
.pipe(concat('spec.js'))
.pipe(gulp.dest('./specs'));
});
gulp.task('default', ['browserify']);
For more details about how this work, this article that I wrote goes more in-depth: http://medium.com/#sogko/gulp-browserify-the-gulp-y-way-bb359b3f9623
For start, you can write a suite.js to require all the tests which you want to run and browserify them.
You can see two examples from my project https://github.com/mallim/sbangular.
One example for grunt-mocha-phantomjs
https://github.com/mallim/sbangular/blob/master/src/main/resources/js/suite.js
One example for protractor
https://github.com/mallim/sbangular/blob/master/src/main/resources/js/suite.js
This is just a start and I am sure there are more fancy ways available.
A little more complicated example to build files by glob pattern into many files with watching and rebuilding separated files. Not for .coffee, for es2015, but not a big difference:
var gulp = require("gulp");
var babelify = require("babelify");
var sourcemaps = require("gulp-sourcemaps");
var gutil = require("gulp-util");
var handleErrors = require("../utils/handleErrors.js");
var browserify = require("browserify");
var eventStream = require("event-stream");
var glob = require("glob");
var source = require("vinyl-source-stream");
var buffer = require("vinyl-buffer");
var watchify = require("watchify");
var SRC_PATH = "./src";
var BUILD_PATH = "./build";
var bundle = function (bundler, entryFilepath) {
console.log(`Build: ${entryFilepath}`);
return bundler.bundle()
.on("error", handleErrors)
.pipe(source(entryFilepath.replace(SRC_PATH, BUILD_PATH)))
.pipe(buffer())
.on("error", handleErrors)
.pipe(
process.env.TYPE === "development" ?
sourcemaps.init({loadMaps: true}) :
gutil.noop()
)
.on("error", handleErrors)
.pipe(
process.env.TYPE === "development" ?
sourcemaps.write() :
gutil.noop()
)
.on("error", handleErrors)
.pipe(gulp.dest("."))
.on("error", handleErrors);
};
var buildScripts = function (done, watch) {
glob(`${SRC_PATH}/**/[A-Z]*.js`, function (err, files) {
if (err) {
done(err);
}
var tasks = files.map(function (entryFilepath) {
var bundler = browserify({
entries: [entryFilepath],
debug: process.env.TYPE === "development",
plugin: watch ? [watchify] : undefined
})
.transform(
babelify,
{
presets: ["es2015"]
});
var build = bundle.bind(this, bundler, entryFilepath);
if (watch) {
bundler.on("update", build);
}
return build();
});
return eventStream
.merge(tasks)
.on("end", done);
});
};
gulp.task("scripts-build", function (done) {
buildScripts(done);
});
gulp.task("scripts-watch", function (done) {
buildScripts(done, true);
});
Complete code here https://github.com/BigBadAlien/browserify-multy-build

registerMultiTask.js in grunt-horde

I'm trying to configure grunt-horde so that I can have multiple builds all using a centrally managed task configuration.
The documentation provides the following example of a registerMultiTasks.js file, but I can't get it to work
module.exports = function(grunt) {
var myMultiTask = require('./multi-tasks/secret-sauce.js');
return {
myMultiTask: ['some description', myMultiTask]
};
};
Even if I replace their example with something more simple:
module.exports = function(grunt) {
return {
demo: ['Demo', function() {
console.info('hello');
}]
};
};
When I run grunt demo:test the output is:
Running "demo:test" (demo) task
Verifying property demo.test exists in config...ERROR
>> Unable to process task.
Warning: Required config property "demo.test" missing. Use --force to continue.
Aborted due to warnings.
When I run grunt --help the demo task shows up in the list. Thinking about the warning message I've also tried the following, but again with no luck.
module.exports = function(grunt) {
return {
demo: ['Demo', function(){
return {test: function(){console.info('hello');}};
}]
};
};
...what am I doing wrong?
I figured it out - you need to define the configuration for each target of the multitasks:
initConfig/demo.js
module.exports = function() {
'use strict';
return {
test: {
foo: 'bar'
}
};
};
You can then access this configuration data and the target from within the multitask function:
registerMultiTask.js
module.exports = function(grunt) {
return {
demo: ['Demo', function() {
grunt.log.writeln('target: ' + this.target);
grunt.log.writeln('foo: ' + this.data.foo);
}]
};
};

Resources