Durandal optimization with Gulp and Gulp-Durandal not working - requirejs

We are building an application with Durandal which is quite big at the moment and we currently looking into bundling all JS files located in the App folder into a main-built.js file. Pretty basic and usual stuff I guess.
I'm using Gulp with the Gulp-Durandal extension. Here our gulpfile :
var gulp = require('gulp');
var durandal = require('gulp-durandal');
gulp.task('build-portal', function () {
durandal({
baseDir: 'app',
main: 'main.js',
output: 'main-built.js',
almond: false,
minify: false
}).pipe(gulp.dest('app'));
});
And here's a snippet of our main.js file
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions'
},
shim: {
},
waitSeconds: 0
});
define('jquery', [], function () { return jQuery; });
define('knockout', [], function () { return ko; });
define('ga', function () { return ga; });
define(
["require", "exports", "durandal/app", "durandal/viewLocator", "durandal/system", "plugins/router", "services/logger", "modules/knockout.extensions", "modules/knockout.validation.custom"],
function (require, exports, __app__, __viewLocator__, __system__, __router__, __logger__, __koExtensions__, __koValidationCustom__) {
var app = __app__;
var viewLocator = __viewLocator__;
var system = __system__;
var router = __router__;
As you can see in the gulpfile, we do not want to use Almond but RequireJs instead, for some reasons almond isn't workin with our project and anyhow, we prefer RequireJs whether its bigger than almond at the end. That's where it look to brake. Running the command to build the main-built.js file took sometime but at the end I get the file built with everything in it.
The problem is that when I try to load the application, it is stuck to the loading screen. It doesn't go any further and there's no errors at all in the browser console.
I created a new project on the side to test if our code was somewhat faulty and found that it might not. You can found that project here :
https://github.com/maroy1986/DurandalGulpBundling
If I build that project with almond option to true, everything works fine but if I switch almound off to tell gulp to use RequireJs, I got the same behavior as our app. You got stuck at the loading screen, without any errors.
So here I am, I do read a lot on the subject but didn't found anything to solve this. Hope someone here already encounter these behavior and have a solution to share.
Thanks!

I had the same requirement and issue. It seems require.js wasn't calling the main module which will startup the Durandal app, that's why it's stuck in the loading screen. I was able to resolve it by implicitly calling the main module:
gulp.task("durandal", function() {
return durandal({
baseDir: "app",
main: "main.js",
output: "main-built.js",
almond: false,
minify: true,
rjsConfigAdapter: function(config) {
//Tell requirejs to load the "main" module
config.insertRequire = ["main"];
return config;
}
})
.pipe(gulp.dest("dist"));
});

I downloaded your project and tried building it with the latest versions of gulp and durandal. Initially it didn't build and gave me the following error:
TypeError: Cannot read property 'normalize' of undefined
This is a problem with the text-plugin of rjs and you can solve this by adding the following to your gulp-file (next to the almond, minify, output... properties):
rjsConfigAdapter : function(rjsConfig){
rjsConfig.deps = ['text'];
return rjsConfig;
}
Once I did that, the build finished and I could build with or without minify, almond and require and the application works fine.

Related

"Object.defineProperty(exports, "__esModule", { value: true });" blocks functions execution in FunctionFile

I started a Office Web Add-in with Typescript&React project by following this tutorial: https://github.com/OfficeDev/office-js-docs-pr/blob/master/docs/includes/file-get-started-excel-react.md . Any taskpane function and page works properly, but functions on the function-file page cannot be properly executed.
By deleting code, I found Object.defineProperty(exports, "__esModule", { value: true }); is one of line in compiled function-file.js casing the problem. Whenever it presents, any function in the file won't be executed. Fiddler shows the script is correctly loaded in Excel without any warning. Status bar shows "[add-in name] is working on your [function name]".
This line of code is generated by Typescript Compiler, in this case, for loading Node module '#microsoft/office-js-helpers'. I tried to modify tsconfig.json file to avoid generating that line, but then the import of '#microsoft/office-js-helpers' fails. In addition, Webpack 4 will add webpackBootstrap code blocking functions in this file. At this point, I can only avoid any import in function-file.ts and do a 'tsc' after building the project by Webpack.
My question is: what is the correct way to setup this project so function-file.js does not contain any code blocking its functions being executed?
If there is no clear answer, at least, why this line of code causes problem where other pages work fine?
The following is my tsconfig.json which can avoid that line but cannot load any module:
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es2015", "dom"],
"typeRoots": ["node_modules/#types"]
},
I manually edit the compiled function-file.js into two versions:
Object.defineProperty(exports, "__esModule", { value: true });
(function () {
Office.initialize = function () { }
};
})();
function writeText(event) {
Office.context.document.setSelectedDataAsync('test');
event.completed();
}
VS
(function () {
Office.initialize = function () { }
};
})();
function writeText(event) {
Office.context.document.setSelectedDataAsync('test');
event.completed();
}
The first one has this problem whereas the second one doesn't.
With some hints from my colleague who used to work on JavaScript during a lunch talk, I made some progress of calling functions in function-file.ts. I wish my path of getting this work would help other people suffering the same pain as I did and still do on this project.
First of all, once I got the function-file.js works properly, I noticed there are two different behaviours when a function does not work:
status bar shows "[add-in name] is working on your [function name]" and stays with it, I believe the function is called but not the line of event.completed() couldn't be reached;
status bar flashes the same message and becomes blank, which indicates the function is not even been found.
Please correct me if there is a better way to diagnose this file.
The original Yeoman generated Webpack configuration of function-file.html is something like this:
new HtmlWebpackPlugin({
title: 'demo',
filename: 'function-file/function-file.html',
template: '../function-file/function-file.html',
chunks: ['function-file']
}),
In order to use any module, 'vendor'(not necessary for my custom modules, but needed by 'office-js-helpers'?) and 'polyfills' entry needs to be included in chunks as well.
Mine Webpack 4 configuration is:
new HtmlWebpackPlugin({
title: "demo",
filename: "function-file/function-file.html",
template: "../function-file/function-file.html",
chunks: ["babel-polyfill", "function-file/function-file"]
}),
The last step is making sure functions declared in function-file.ts can be found: asking Webpack to export global functions in function-file.ts, which I am still not sure if I am hacking Typescript development or doing fine.
Sample function-file.ts:
import * as OfficeHelpers from '#microsoft/office-js-helpers';
(() => {
Office.initialize = () => {};
})();
declare global {
namespace NodeJS {
interface Global {
writeText: (event: Office.AddinCommands.Event) => void;
}
}
}
global.writeText = (event: Office.AddinCommands.Event) => {
Office.context.document.setSelectedDataAsync('test');
event.completed();
};
Notice: even office-js-helpers is imported, some of functions are still not working. I tested my custom modules, they are working properly.
I really wish there are some function-file examples on NodeJS hosted React&Typescript project for Office Web Add-in, as detail configuration is really different from ordinary NodeJS + JavaScript project.

requireJS module.config() is missing after optimisation

Running into an issue when I run the RequireJS optimizer... I have the following defined in my main config file:
require.config({
config: {
bootstrap: {
autoload: true
}
},
...
}
Then in my bootstrap.js file I do:
define(['module', 'app'], function (module, App) {
'use strict';
console.log(module.config().autoload);
...
});
But autoload is undefined :( The variable is available in the non-optimised version however.
I've tried this with other modules and other variables, but essentially no module config is being pushed through into the output file from Require's Optimizer.
Is this expected behaviour ?
I know this is a bit late, but I had spent close to half a day struggling with the same problem and finally found that this worked:
Apparently config.js file or the main config is compile time configuration and wont be part of run/build config. So the solution is to add the module config to the top of the main.js file using requirejs.config({}); (this is for the case that it already worked with config prior to compiling and minifying/uglifying).
eg:
at top of main.js add:
requirejs.config({
config: {
bootstrap: {
autoload: true
}
}
};)

Using Gulp to build requireJS project - gulp-requirejs

I am trying to use gulp-requirejs to build a demo project. I expect result to be a single file with all js dependencies and template included. Here is my gulpfile.js
var gulp = require('gulp');
var rjs = require('gulp-requirejs');
var paths = {
scripts: ['app/**/*.js'],
images: 'app/img/**/*'
};
gulp.task('requirejsBuild', function() {
rjs({
name: 'main',
baseUrl: './app',
out: 'result.js'
})
.pipe(gulp.dest('app/dist'));
});
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['requirejsBuild']);
The above build file works with no error, but the result.js only contains the content of main.js and config.js. All the view files, jquery, underscore, backbone is not included.
How can I configure gulp-requirejs to put every js template into one js file?
If it is not the right way to go, can you please suggest other method?
Edit
config.js
require.config({
paths: {
"almond": "/bower_components/almond/almond",
"underscore": "/bower_components/lodash/dist/lodash.underscore",
"jquery": "/bower_components/jquery/dist/jquery",
"backbone": "/bower_components/backbone/backbone",
"text":"/bower_components/requirejs-text/text",
"book": "./model-book"
}
});
main.js
// Break out the application running from the configuration definition to
// assist with testing.
require(["config"], function() {
// Kick off the application.
require(["app", "router"], function(app, Router) {
// Define your master router on the application namespace and trigger all
// navigation from this instance.
app.router = new Router();
// Trigger the initial route and enable HTML5 History API support, set the
// root folder to '/' by default. Change in app.js.
Backbone.history.start({ pushState: false, root: '/' });
});
});
The output is just a combination this two files, which is not what I expected.
gulp-requirejs has been blacklisted by the gulp folks. They see the RequireJS optimizer as its own build system, incompatible with gulp. I don't know much about that, but I did find an alternative in amd-optimize that worked for me.
npm install amd-optimize --save-dev
Then in your gulpfile:
var amdOptimize = require('amd-optimize');
var concat = require('gulp-concat');
gulp.task('bundle', function ()
{
return gulp.src('**/*.js')
.pipe(amdOptimize('main'))
.pipe(concat('main-bundle.js'))
.pipe(gulp.dest('dist'));
});
The output of amdOptimize is a stream which contains the dependencies of the primary module (main in the above example) in an order that resolves correctly when loaded. These files are then concatenated together via concat into a single file main-bundle.js before being written into the dist folder.
You could also minify this file and perform other transformations as needed.
As an aside, in my case I was compiling TypeScript into AMD modules for bundling. Thinking this through further I realized that when bundling everything I don't need the asynchronous loading provided by AMD/RequireJS. I am going to experiment with having TypeScript compile CommonJS modules instead, then bundling them using webpack or browserify, both of which seem to have good support within gulp.
UPDATE
My previous answer always reported taskReady even if requirejs reported an error. I reconsidered this approach and added error logging. Also I try to fail the build completely as described here gulp-jshint: How to fail the build? because a silent fail really eats your time.
See updated code below.
Drew's comment about blacklist was very helpfull and gulp folks suggest using requirejs directly. So I post my direct requirejs solution:
var DIST = './dist';
var requirejs = require('requirejs');
var requirejsConfig = require('./requireConfig.js').RJSConfig;
gulp.task('requirejs', function (taskReady) {
requirejsConfig.name = 'index';
requirejsConfig.out = DIST + 'app.js';
requirejsConfig.optimize = 'uglify';
requirejs.optimize(requirejsConfig, function () {
taskReady();
}, function (error) {
console.error('requirejs task failed', JSON.stringify(error))
process.exit(1);
});
});
The file at ./dist/app.js is built and uglified. And this way gulp will know when require has finished building. So the task can be used as a dependency.
My solution works like this:
./client/js/main.js:
require.config({
paths: {
jquery: "../vendor/jquery/dist/jquery",
...
},
shim: {
...
}
});
define(["jquery"], function($) {
console.log($);
});
./gulpfile.js:
var gulp = require('gulp'),
....
amdOptimize = require("amd-optimize"),
concat = require('gulp-concat'),
...
gulp.task('scripts', function(cb) {
var js = gulp.src(path.scripts + '.js')
.pipe(cached('scripts'))
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(remember('scripts'))
.pipe(amdOptimize("main",
{
name: "main",
configFile: "./client/js/main.js",
baseUrl: './client/js'
}
))
.pipe(concat('main.js'));
.pipe(gulp.dest(path.destScripts));
}
...
This part was important:
configFile: "./client/js/main.js",
baseUrl: './client/js'
This allowed me to keep my configuration in one place. Otherwise I was having to duplicate my paths and shims into gulpfile.js.
This works for me. I seems that one ought to add in uglification etc via gulp if desired. .pipe(uglify()) ...
Currently I have to duplicate the config in main.js to run asynchronously.
....
var amdOptimize = require("amd-optimize");
...
var js = gulp.src(path.scripts + '.js')
.pipe(cached('scripts'))
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(remember('scripts'))
.pipe(amdOptimize("main",
{
name: "main",
paths: {
jquery: "client/vendor/jquery/dist/jquery",
jqueryColor: "client/vendor/jquery-color/jquery.color",
bootstrap: "client/vendor/bootstrap/dist/js/bootstrap",
underscore: "client/vendor/underscore-amd/underscore"
},
shim: {
jqueryColor : {
deps: ["jquery"]
},
bootstrap: {
deps: ["jquery"]
},
app: {
deps: ["bootstrap", "jqueryColor", "jquery"]
}
}
}
))
.pipe(concat('main.js'));
Try this code in your gulpfile:
// Node modules
var
fs = require('fs'),
vm = require('vm'),
merge = require('deeply');
// Gulp and plugins
var
gulp = require('gulp'),
gulprjs= require('gulp-requirejs-bundler');
// Config
var
requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('app/config.js') + '; require;'),
requireJsOptimizerConfig = merge(requireJsRuntimeConfig, {
name: 'main',
baseUrl: './app',
out: 'result.js',
paths: {
requireLib: 'bower_modules/requirejs/require'
},
insertRequire: ['main'],
// aliases from config.js - libs will be included to result.js
include: [
'requireLib',
"almond",
"underscore",
"jquery",
"backbone",
"text",
"book"
]
});
gulp.task('requirejsBuild', ['component-scripts', 'external-scripts'], function (cb) {
return gulprjs(requireJsOptimizerConfig)
.pipe(gulp.dest('app/dist'));
});
Sorry for my english. This solution works for me. (I used gulp-requirejs at my job)
I think you've forgotten to set mainConfigFile in your gulpfile.js. So, this code will be work
gulp.task('requirejsBuild', function() {
rjs({
name: 'main',
mainConfigFile: 'path_to_config/config.js',
baseUrl: './app',
out: 'result.js'
})
.pipe(gulp.dest('app/dist'));
});
In addition, I think when you run that task in gulp, require can not find its config file and
This is not gulp-requirejs fault.
The reason why only main.js and config.js is in the output is because you're not requiring/defining any other files. Without doing so, the require optimizer wont understand which files to add, the paths in your config-file isn't a way to require them!
For example you could load a main.js file from your config file and in main define all your files (not optimal but just a an example).
In the bottom of your config-file:
// Load the main app module to start the app
requirejs(["main"]);
The main.js-file: (just adding jquery to show the technique.
define(["jquery"], function($) {});
I might also recommend gulp-requirejs-optimize instead, mainly because it adds the minification/obfuscation functions gulp-requirejs lacks: https://github.com/jlouns/gulp-requirejs-optimize
How to implement it:
var requirejsOptimize = require('gulp-requirejs-optimize');
gulp.task('requirejsoptimize', function () {
return gulp.src('src/js/require.config.js')
.pipe(requirejsOptimize(function(file) {
return {
baseUrl: "src/js",
mainConfigFile: 'src/js/require.config.js',
paths: {
requireLib: "vendor/require/require"
},
include: "requireLib",
name: "require.config",
out: "dist/js/bundle2.js"
};
})).pipe(gulp.dest(''));
});

How to get a single javascript page using r.js

I am doing my first try using requireJS and it works great !
I now would like to use the optimizer and i meet some issues when running my code in the browser.
I have these JS files:
/public/javascripts/build.js
/public/javascripts/main.js
/public/javascripts/lib/jquery.min.js
/public/javascripts/lib/require.min.js
/public/javascripts/a.js
/public/javascripts/b.js
/public/javascripts/c.js
a.js, b.js and c.js are modules i define for my application using requireJS.
main.js:
require.config({
paths: {
'jQuery': 'lib/jquery.min.js'
},
shim: {
'jQuery': {
exports: '$'
}
}
});
require(['a.js'], function(A){
var Entity = new A();
});
build.js
({
baseUrl: ".",
paths: {
requireLib: "lib/require.min",
jquery: "lib/jquery.min"
},
name: "main",
out: "main-built.js",
include: ["requireLib"]
})
Also i am wondering why do we have to specify the paths of the libraries into the build.js and not the other javascript files.
When i do not use the optimizer and only load the file
<script src="/javascripts/lib/require.min.js" data-main="/javascripts/main"></script>
it works great, but when i run r.js -o ./public/javascripts/build.js and only load
<script src="/javascripts/main-built.js"></script> i get the error Uncaught TypeError: undefined is not a function in the minified code.
How to explain that ?
Here are the logs i get when running r.js
Tracing dependencies for: main
Uglifying file: /public/javascripts/main-built.js
/public/javascripts/main-built.js
----------------
/public/javascripts/lib/require.min.js
/public/javascripts/a.js
/public/javascripts/b.js
/public/javascripts/lib/jquery.min.js
/public/javascripts/c.js
/public/javascripts/main.js
This is definitely wrong:
require(['a.js'], function(A){
var Entity = new A();
});
You should not use extensions in the list of dependencies you give to require or define. Modules should be named without extension. So here 'a', not 'a.js'. Using 'a.js' will cause RequireJS to fail loading what you really want once the optimizer has run. Let's say you have a file named a.js which has:
define(function () {
return function () {};
});
The optimizer will include it into your main-built.js file like this:
define("a", function () {
return function () {};
});
Note how the first parameter to define is now "a". This has been added by r.js. This is the name of the module. When you load main-built.js, a module named "a" is defined. When you use require with "a.js", you are telling RequireJS you want something in a file named a.js so RequireJS will go looking for that and ignore what is in main-built.js.
Also, jQuery 1.8 or over does not need a shim.
I just have added
shim: {
'jQuery': {
exports: '$'
}
}
into the build.js file, and it works perfectly !
Thanks !

r.js optimizer resolving configured deps files

I try requireJS optimizer to pack all my scripts into one file and I cannot overcome one issue.
My requireJs configuration is
var require = {
// 'baseUrl': 'static/scripts',
'paths': {
'external': 'global/external'
},
'waitSeconds': 2,
// 'enforceDefine': true,
'deps': ['external/jquery-1.7.2'],
'config': {
}
};
requireJs will load everything that is in deps before it starts loading any other scripts. since jquery wraps itself with define function and with name jquery I can load it to my scripts simply by calling
var var $ = require('jquery');
This works great when code is not optimized.
PROBLEM:
when I run r.js (with node - but this I think is irrelevant) optimizer prints error that it cannot resolve jquery dependency.
There is nothing in requireJs optimizer faq on that. I tried play with configuring 'path' property but it didnt fix anything.
I removed deps property and added new element to paths
var require = {
// 'baseUrl': 'static/scripts',
'paths': {
'external': 'global/external'
'jquery': 'global/external/jquery-1.7.2'
},
'waitSeconds': 2,
...
};
it didnt play before because I tried to setup path to jquery like
'jquery': 'external/jquery-1.7.2'
thinking that external should evaluate to
'global/external/jquery-1.7.2'
then I just set path.jquery in build script (or as argument to r.js) once again and it worked

Resources