How should one package the HTML templates (aka 'partials') in an Angular.js app when it is concatenated + minified for distribution? Is there a good way of including them in that single file, or should I zip the folder which contains the main.js, /templates/*.html and /styles/app.css subdirectories?
The Require.js optimizer compresses all the JS files to a single file myapp.min.js that is suitable for distribution, but the HTML files are still referenced as individual files.
As background, the code is in a Node.js server:
public/
/app/main.js
/app/app.js
/app/directives/*.js
/app/filters/*.js
/app/factories/*.js
/app/other/*.js
/app/templates/*.html
After running r.js, all the files in public/app/** have optimized twins located in a different directory public/build/**. But the browser still looks for the templates in their original location, /app/templates/*.html.
So presumably the client would have to put them in that same directory, which seems like an unfair constraint.
Minifying RequireJS Javascript codebase to a single file
You have to use the templates through the text! plugin of RequireJS. E.g. for a directive:
define(["text!app/templates/a.html", "myAngularModule"],
function(template, myAngularModule) {
myAngularModule.directive("theDirective", function() {
return {
template: template, // NOTE HERE
...
};
});
});
Or a route:
define(["text!app/templates/a.html", "myAngularModule"],
function(template_a, myAngularModule) {
myAngularModule.config(function($routeProvider) {
$routeProvider.when("some/path", {
template: template_a, // NOTE HERE
...
});
...
});
});
Alternative, using templateUrl (just in case it is needed):
define(["text!app/templates/a.html", "myAngularModule"],
function(template_a, myAngularModule) {
myAngularModule.config(function($routeProvider) {
$routeProvider.when("some/path", {
templateUrl: "app/templates/a.html", // can be any value actually
...
});
...
});
myAngularModule.run(function($templateCache) {
$templateCache.put(
"app/templates/a.html", // NAME MUST MATCH NAME ABOVE
template_a
);
});
});
Related
I am facing a weird problem right now. So, I am trying to test production build, and the build files generated contain a ?v=${some random number here} to prevent caching.
However, the browser-sync doesn't load these files.
The way I figured that out is because is that I have in total 3 files, 2 script files and one stylesheet. One of the script files and the stylesheet have the query string generated.
The script file that doesn't contain the query string gets loaded.
My folder structure is
/
index.html
assets/
dist/
mystyle.css?v=1237791723
myscript.js?v=123576231612
And the two gulp tasks are
gulp.task('localScripts', function() {
return gulp.src(['assets/js/app.js', 'node_modules/owl.carousel/dist/owl.carousel.min.js', 'assets/dist/js/style.js'])
.pipe(babel({ presets: ['babili'] }))
.pipe(concat(`myscript.js?v=${date}`))
.pipe(gulp.dest(dist))
})
gulp.task('joinCss', function() {
return gulp.src(['node_modules/bootstrap/dist/css/bootstrap.min.css', 'assets/dist/css/style.min.css'])
.pipe(concat(`mystyle.css?v=${date}`))
.pipe(gulp.dest(dist))
})
And one replace html task which I use to generate the build index.html is
gulp.task('replaceHtml', function () {
gulp.src('./start.html').pipe(htmlReplace({
'js': ['assets/dist/scripts.js', `assets/dist/myscript.js?v=${date}`],
'css': `assets/dist/mystyle.css?v=${date}`
})).pipe(rename('index.html'))
.pipe(gulp.dest('./'))
});
And, the final generated index.html has the desired files in place:
<link rel="stylesheet" href="assets/dist/mystyle.css?v=1508761436559">
<script src="assets/dist/scripts.js"></script>
<script src="assets/dist/myscript.js?v=1508761436559"></script>
And my browser-sync config is
gulp.task('server', [], function() {
browserSync.init({
server: {
baseDir: "./"
},
port: 9001,
});
});
And when I start the server, only scripts.js gets loaded.
Can anyone explain why?
I am using the latest version of browser-sync
Project Structure
root
wwwroot <-- files under this location are static files public to the site
css
lib
bootstrap/js/bootstrap.js
jquery/js/jquery.js
knockout/knockout.js
requires/require.js
scripts
modules ┌───────────────┐
global.js <--│ Built modules │
dropdown.js └───────────────┘
modules
global.js ┌────────────────┐
dropdown <--│ Source modules │
dropdown.js └────────────────┘
gruntfile.js
global.cs Contents (pre-built version at ~/modules/global.js)
require.config({
baseUrl: "scripts/modules",
paths: {
jquery: "../../lib/jquery/js/jquery",
bootstrap: "../../lib/bootstrap/js/bootstrap",
knockout: "../../lib/knockout/knockout"
},
shims: {
bootstrap: {
deps: ['jquery']
}
},
});
define(function (require) {
var $ = require('jquery');
var ko = require('knockout');
var bootstrap = require('bootstrap');
});
dropdown.js Contents (pre-built version at ~/modules/dropdown.js)
define(function () {
console.log('dropdown initialized');
return 'foo';
});
HTML Page
Contains this script tag in the <head> of the page for loading requires config:
<script src="~/lib/requirejs/require.js" data-main="scripts/modules/global"></script>
In the body of the HTML page, I have the following:
<script>
require(['global'], function () {
require(['dropdown'], function (dropdown) {
console.log(dropdown);
});
});
</script>
Issue
The dropdown callback is undefined instead of the expected "foo" string that I'm returning from the defined module.
In fact, the console does not contain a log item for "dropdown initialized" either. This makes me believe the module is not being invoked somehow? However, it's strange the dropdown.js is present in F12 debugger as a script loaded into the page. Therefore, requires did make a call to load it, but did not run the contents of the define?
Noteworthy mentions
I'm using r.js to optimize and build. Both global.js and dropdown.js are processed over.
The name assigned to the dropdown module by r.js processing is "modules/dropdown/dropdown.js". I'm unsure if I should be using this somehow, or if I'm referring to the module correctly as just dropdown and relying on my baseUrl config having the correct path.
Edit #1
I have added the r.js build configuration used with grunt per commenter request. In conjunction, I updated the file structure to include the overall project structure, instead of just the runtime public wwwroot structure.
The r.js process will compile built forms of global.js + other modules in ~/wwwroot/scripts/modules from the source location ~/modules in summary.
function getRequireJsConfiguration() {
var baseUrl = './';
var paths = {
jquery: "wwwroot/lib/jquery/js/jquery",
bootstrap: "wwwroot/lib/bootstrap/js/bootstrap",
knockout: "wwwroot/lib/knockout/knockout"
};
var shims = {
bootstrap: {
deps: ['jquery']
}
};
var optimize = 'none';
var configuration = {};
var jsFilePaths = grunt.file.expand('modules/**/*.js');
jsFilePaths.forEach(function (jsFilePath) {
var fileName = jsFilePath.split('/').pop();
if (configuration[fileName]) {
throw 'Duplicate module name conflict: ' + fileName;
}
configuration[fileName] = {
options: {
baseUrl: './',
name: jsFilePath,
out: 'wwwroot/scripts/modules/' + fileName,
paths: paths,
shims: shims,
optimize: optimize,
exclude: ['jquery', 'knockout', 'bootstrap']
}
};
});
configuration['global'] = {
options: {
baseUrl: './',
name: 'modules/global.js',
out: 'wwwroot/scripts/modules/global.js',
paths: paths,
shims: shims,
optimize: optimize,
}
};
return configuration;
}
Edit #2
Thought it'd be a good idea to include the versions of requirejs packages I'm using:
requirejs: 2.1.15
grunt-contrib-requirejs: 0.4.4
Thanks.
The name assigned to the dropdown module by r.js processing is "modules/dropdown/dropdown.js". I'm unsure if I should be using this somehow, or if I'm referring to the module correctly as just dropdown and relying on my baseUrl config having the correct path.
In a sense, yes, you should be using that full path. That's what Require refers to as the module id - "modules/dropdown/dropdown" (if the .js in the above output was real, I suggest stripping that extension in the "name" config. .js is assumed by RequireJS, you don't want that string in your module ids). The basePath is used, when given IDs, to transform some unknown ID to a file path (e.g. 'bootstrap' id -> (applying path config) -> '../../lib/bootstrap/js/bootstrap' -> (applying base URL) -> 'scripts/modules/../../lib/bootstrap/js/bootstrap').
Really, though, just allowing r.js to concatenate everything into one file
is the preferred way to go. You could use the include option to include modules un-referenced by global.js in with the optimized bundle, too ( https://github.com/jrburke/r.js/blob/master/build/example.build.js#L438 )
As to your specific problem: your lazy require(['dropdown']) call is misleading you. By combining the requested module id with the basePath, RequireJS comes up with the URL you want - scripts/modules/dropdown - which defines a module with the module id scripts/module/dropdown - but since you requested the module id dropdown, you get nothing. (I would've guessed you'd get a RuntimeError instead of undefined, but I suppose that's how things go). One way or another you need to address the id/path mismatches.
Although I have resolved my issue with the hints wyantb's answer provided, I've since changed my approach to a single file concat due to the simplicity it brings. I still wanted to post the specifics of how I solved this question's issue for anyone else to happens along it.
In the grunt build configuration options, I added the onBuildWrite field to transform the content, so my assigned module IDs lined up with how I was lazily loading them.
onBuildWrite: function (moduleName, path, contents) {
return contents.replace(/modules\/global.js/, 'global');
}
This code is specifically for the global.js file. I implemented a similar onBuildWrite for the other module files (in the foreach loop). The transformation will essentially strip the path and extension from the module name that r.js assigns.
Here are some examples of before and after:
Before After
/modules/global.js global
/modules/dropdown/dropdown.js dropdown
/modules/loginButton/loginButton.js loginButton
Therefore, when I load the modules using the HTML script from my original question, requirejs resolves and finds a match.
Either require by path or define global and dropdown in global.cs
require(['./global'], function () {
require(['./dropdown'], function (dropdown) {
console.log(dropdown);
});
});
I'm trying to use jQuery file upload, but I'm getting stuck with the RequireJS configuration. We install our dependencies in a /ext/ folder, e.g:
/src
/ext
/jquery-file-upload
In my main.js I use the following config:
require.config({
paths: {
"ext/jquery-file-upload": "../ext/jquery-file-upload/js/jquery.fileupload"
}
});
require([
"ext/jquery-file-upload"
]);
But then RequireJS tries to load jquery.ui.widget.js from the root instead of as a relative file. It is located in the jquery-file-upload directory..
Does anyone know what I'm doing wrong, or does anyone know of a working RequireJS config for jQuery file upload?
Thanks,
Martijn
If you look at the jquery.fileupload.js file, at the top it declares its own dependencies
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'jquery.ui.widget'
], factory);
You need to edit your require.config path for the jquery.ui.widget item.
require.config({
paths: {
'jquery.ui.widget': 'your_path_here/jquery.ui.widget'
}
});
If I don't mistake, your problem is that your plugin - jquery-file-upload - tries to load its dependencies by itself.
The problem is that filepaths in JS are relative to the page loading the script file, not to the file itself (Relative Paths in Javascript in an external file). This explains why your file seems to be loaded relatively to the root and not the given path.
In this case, you probably will have to manipulate a bit of the plugin code and take a look at the explanations given here concerning requireJS and dependencies loading:
How do I use requireJS and jQuery together? .
Try mapping jquery.widget.ui to the correct path...
require.config({
paths: {
"ext/jquery-file-upload": "../ext/jquery-file-upload/js/jquery.fileupload"
"jquery.ui.widget": "../ext/jquery-file-upload/js/vendor/jquery.ui.widget"
}
});
require([
"ext/jquery-file-upload"
]);
For anyone else having this problem...this is the config that worked for us.Hope it helps someone
The paths declaration
paths: {
'jquery.ui.widget': '../../Scripts/FileUpload/jqueryui/jquery.ui.widget',
'jquery_iframe_transport': '../../Scripts/FileUpload/jquery.iframe-transport',
'jquery.fileupload': '../../Scripts/FileUpload/jquery.fileupload'
}
The Define statement
define(['jquery.ui.widget','jquery_iframe_transport','jquery.fileupload'])
The above is dependent on jquery being loaded. We are using Durandal hence dont need a shim but you will need to ensure jquery is loaded before anything else
The initialisation in code
function uploadFile() {
var url = '/Backload/UploadHandler';
$('#fileupload').fileupload({
url: url,
dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<p/>').text(file.name).appendTo('#files');
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
}
});
}
This is the basic upload example..
I'm using requirejs and AMD modules for my TypeScript project, with something like 20 different source files at the moment and likely to grow substantially. All of this works, but it's very slow to load all 20 files, so it would be better to have them minified. But because of how requirejs wants to load everything, it seems like it's going to require that I keep the modules in separate files - I don't think I can just take the generated module1.js and module2.js files and minify them into one file and then have requirejs load those without changing some code. (I could be wrong on this.)
The other way that I see to do this is to use the r.js file that requirejs provides to merge all the different files together in a way that still keeps requirejs happy. But r.js requires node.js, and I'd rather not introduce that as a dependency in my build process if there's any other way to do it.
So before I dive into this and try half a dozen different solutions - how are other folks approaching this with big projects?
What you could do is to implement a thin RequireJS shim to use in a minified build. Depending on how much of the RequireJS API you want to use, you could get by with very little. For simplicity you could also use named modules.
Say, while developing you use RequireJS to load your modules. When you want to make a minified build, you could simply include a simple loader in the minified file.
If you have files app.js, foo.js and bar.js as follows:
//from app.js
define("app", ["foo", "bar"], function(foo, bar) {
return {
run: function() { alert(foo + bar); }
}
});
//from foo.js
define("foo", [], function() {
return "Hello ";
});
//from bar.js
define("bar", [], function() {
return "World!";
});
And let's say you minify all those files together. At the top of the file you include the following shim:
//from your-require-shim.js
(function(exports) {
var modules = {};
var define = function(name, dependencies, func) {
modules[name] = {
name:name,
dependencies:dependencies,
func:func,
result:undefined
};
};
var require = function(name) {
var module = modules[name];
//if we have cached result -> return
if(module.result) { return module.result; }
var deps = [];
//resolve all dependencies
for(var i=0,len=module.dependencies.length;i<len;i++) {
var depName = module.dependencies[i];
var dep = modules[depName];
if(!dep.result) {
//resolve dependency
require(depName);
}
deps.push(dep.result);
}
module.result = module.func.apply(this, deps );
return module.result;
};
exports.require = require;
exports.define = define;
}(window));
And execute the module defined in app.js
require("app").run();
Like in this fiddle.
It's a crude PoC of course, but I'm sure you get the meaning.
If you are using ASP.NET MVC 4, you can make a bundle which will minify everything when you deploy to production in a set of files or in a folder. You'll find more info on bundles here.
My project includes the following files:
./index.html
./js/main.js
./js/vendor/require.js
./js/viewmodel/vm.js
The index.html has the following relevant snippet:
<script data-main="js/main.js" src="js/vendor/require.js"></script>
<script type="text/javascript">
require(['viewmodel/vm', 'ko'],
function(viewmodel, ko) {
ko.applyBindings(viewmodel);
}
);
</script>
The js/main.js file is as follows:
var root = this;
define('jquery', ['http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.3.js'], function () { return root.$; });
define('ko', ['http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js'], function (ko) { return ko; });
The js/viewmodel/vm.js file...
define(['jquery', 'ko'],
function($, ko) {
return {
subject: 'world',
greeting: 'hello'
}
}
);
When you open a browser to index.html, then the browser tries to load a file called js/ko.js instead of using the module defined in main.js. It seems like the js file pointed to by the data-main attribute is not guaranteed to run before dependency resolution. This does not seem correct to me since one purpose of the data-main js file is to define require configuration (i.e. path, shim, etc). I am using require v2.1.2.
This works perfectly fine if I copy the contents of my main.js file into the script block in index.html. By "perfectly fine" I mean that it resolved ko to be a module and finds the appropriate CDN link to resolve ko instead of trying to download ./js/ko.js.
to use the data-main attribute for configuring your whole application, it is necessary that it is the single entry point for all your code.
your 2nd script block breaks this requirement by providing a 2nd entry point. since these entry points will resolve independently of each other (and asynchronously), you cannot rely on one to affect the other.
to resolve it, refactor your code in a way that provides a single entry point to your application and do your configuration via this entry point.
That's because requirejs sets the async. Attribute on the script.
The boolean async attribute on script elements allows the external
JavaScript file to run when it's available, without delaying page load
first.
This means that both scripts are loaded and evaluated parallel, so none of the two scripts can access methods or functions from the other one.
If you want to define requirejs variables in one script you mustn't load that script with require js.
For me there are three possibilities how you can solve that problem:
Add the content of main.js to your page (as you mention)
Load the main.js file without requirejs as normal script
Define the require config before loading the scripts (link to requirejs docu )
I had the same problem. The architecture of the site that i was working was components that was loading asynchronous at each part of the page.
Each component has its own html, css, and js code.
So, my solution is to keep a guard function for all the required dependency code, to protect them from running before the main javascript file:
index.html
<head>
<script type="text/javascript">
window.BeforeMainGuard = {
beforeMainLoadedFunctions: [],
hasMainLoaded: false,
guard: function( func ) {
console.assert( typeof func === 'function' );
if( this.hasMainLoaded ) {
func();
}else {
this.beforeMainLoadedFunctions.push( func );
}
},
onMainLoaded: function() {
for( var i = 0; i<this.beforeMainLoadedFunctions.length; ++i ) {
var beforeMainLoadedFunction = this.beforeMainLoadedFunctions[i];
beforeMainLoadedFunction();
}
this.beforeMainLoadedFunctions = null;
this.hasMainLoaded = true;
}
};
</script>
<script data-main="js/main.js" src="js/vendor/require.js"></script>
<script type="text/javascript">
window.BeforeMainGuard.guard( function() {
require(['viewmodel/vm', 'ko'],
function(viewmodel, ko) {
ko.applyBindings(viewmodel);
}
);
});
</script>
</head>
js/main.js
require.config({
// your config
});
require( [ 'AppLogic' ], function( AppLogic ){
AppLogic.Init();
window.BeforeMainGuard.onMainLoaded();
} );