I'm running into the following problem with requirejs and usemin:
I want to setup a multipage application, where I dynamically load modules that only contain page specific functionality (e.g. about -> about.js, home -> home.js). I could go ahead and pack everything in a single file, but that just leads to a bigger file size and overhead on functionality that isn't necessary on each site! (e.g. why would I need to load a carousel plugin on a page that doesn't have a carousel!)
I checked out the example https://github.com/requirejs/example-multipage-shim
That is in fact a great way to deal with it, until I bring usemin into the game. After revving the filenames the src path of each script tag is updated, but what about the dependencies?
<script src="scripts/vendor/1cdhj2.require.js"></script>
<script type="text/javascript">
require(['scripts/common'], function (common) {
require(['app'], function(App) {
App.initialize();
});
});
</script>
In that case, require.js got replaced by the revved file 1cdhj2.require.js. Great!
But the required modules "common" and "app" no longer work since common became 4jsh3b.common.js and app became 23jda3.app.js!
What can I do about this? Thanks for your help!
(Also using Yeoman, btw)
It's a tricky problem and I'm sure somebody else fixed in in a more elegant way, but the following works for me.
I might publish this as a grunt plugin once it's a little more robust.
Taken from my Gruntfile:
"regex-replace": {
rjsmodules: { // we'll build on this configuration later, inside the 'userevd-rjsmodules' task
src: ['build/**/*.js'],
actions: []
}
},
grunt.registerTask('userevd-rjsmodules', 'Make sure RequireJS modules are loaded by their revved module name', function() {
// scheduled search n replace actions
var actions = grunt.config("regex-replace").rjsmodules.actions;
// action object
var o = {
search: '',
replace: '', //<%= grunt.filerev.summary["build/js/app/detailsController.js"] %>
flags: 'g'
};
// read the requirejs config and look for optimized modules
var modules = grunt.config("requirejs.compile.options.modules");
var baseDir = grunt.config("requirejs.compile.options.dir");
var i, mod;
for (i in modules) {
mod = modules[i].name;
revvedMod = grunt.filerev.summary[baseDir + "/" + mod + ".js"];
revvedMod = revvedMod.replace('.js', '').replace(baseDir+'/','');
o.name = mod;
o.search = "'"+mod+"'";
// use the moduleid, and the grunt.filerev.summary object to find the revved file on disk
o.replace = "'"+revvedMod+"'";
// update the require(["xxx/yyy"]) declarations by scheduling a search/replace action
actions.push(o);
}
grunt.config.set('regex-replace.rjsmodules.actions', actions);
grunt.log.writeln('%j', grunt.config("regex-replace.rjsmodules"));
grunt.task.run("regex-replace:rjsmodules");
}),
You can also use requirejs' map config to specify a mapping between your original module and your revved one.
Filerev outputs a summary object containing a mapping of all the modules that were versioned and their original names. Use grunt file write feature to write a file in AMD way with the contents being the summary object:
// Default task(s).
grunt.registerTask('default', ['uglify', 'filerev', 'writeSummary']);
grunt.registerTask('writeSummary', 'Writes the summary output of filerev task to a file', function() {
grunt.file.write('filerevSummary.js', 'define([], function(){ return ' + JSON.stringify(grunt.filerev.summary) + '; })');
})
and use this file in your require config so that the new revved modules are used instead of old ones:
require(['../filerevSummary'], function(fileRev) {
var filerevMap = {};
for (var key in fileRev) {
var moduleID = key.split('/').pop().replace('.js', '');
var revvedModule = '../' + fileRev[key].replace('.js', '');
filerevMap[moduleID] = revvedModule;
}
require.config({
map: {
'*': filerevMap
}
});
The filerevMap object that I created above is specific to my folder structure. You can tweak it as per yours. It just loops through the filerev summary and makes sure the keys are modified as per your module names and values as per your folder structure.
Related
I have a database table that contains a list of colour variables (example HEX colour code). My styles are compiled using Gulp and SASS.
When my Django app creates/updates a row in the database i need to build a new stylesheet based on the colours.
Somehow i need to get the colours from my server side app into a build process.
Record with colours added -> Gulp runs -> New colour variables are used within the stylesheet generation.
Any ideas how this could be done?
Thanks,
I solved this problem in a less than ideal way..
There is a module called gulp-preprocess which take a context array and replaces vars before the sass process runs..
For example:
SASS File
$body-background: '/* #echo body-background */';
body {
background: $body-background;
}
GULP
var data = {
'1': {
'body-background': '#f00',
},
'2': {
'body-background': '#ffffff',
}
}
gulp.task('scss', function () {
for (var partner_id in data) {
if (!data.hasOwnProperty(partner_id)) continue;
var partner_data = data[partner_id]
gulp.src('./static/scss/*.scss')
.pipe($.sourcemaps.init())
.pipe($.preprocess({context: partner_data}))
.pipe($.sass({
errLogToConsole: true,
style: 'compact'
})
.on('error', function (err) {
console.log('Error:', err);
this.emit('end');
}))
.pipe($.autoprefixer({cascade: false}))
.pipe($.cssnano())
.pipe($.sourcemaps.write('./maps'))
.pipe(gulp.dest('./static/css/'+ partner_id))
}
});
I came across this service https://www.grooveui.com, that lets you create multiple themes from your SASS files.
The only catch is you have to host your SASS files with them. Then you can create new themes and set variable values. I guess they are using database to store variables and generating multiple SASS files.
Could be worth a try.
I am using Handlebars as templating engine for Sailsjs. Basic templating is working fine but I can't find out the way to use Handlebars helper function or even built in functions are not available.
I have managed to solve the issue with partials using following article.
https://github.com/balderdashy/sails/issues/2414
I have also registered the helpers.js in config folder but I can't call any custom, built in blocks or iteration helper function.
Any pointers to solve the issue of helpers will be helpful.
Sailsjs verion - 0.11.4
Handlebars version - 4.0.5
I have registered the helper function in above file like this:
Handlebars.registerHelper('help', function() {
return "help22";
});
And I am calling the same in my template:
{{{help}}}
Any idea why it is not rendering?
OK, after few hours trying, I come up with a solution:
You can add this line to the end of config/helpers.js
module.exports = Handlebars.helpers;
Inside view.js:
module.exports.views = {
engine: 'handlebars',
layout: 'layout',
partials: 'partials',
helpers: require('./helpers')
};
It will work.
Above solution didn't work for me - I got error "Handlebars is not defined" because I didn't check this link- https://github.com/balderdashy/sails/issues/2414
I have had to add Handlebars = require('handlebars'); in /config/helpers.js
Putting all together:
Edit file /config/views.js
module.exports.views = {
engine: 'handlebars',
extension: 'html', // optional
layout: 'layouts/main', // optional, will load /views/layouts/main.html
partials: 'partials', // optional, will load partials from /views/partials/
helpers: require('./helpers') // <-- this is it
};
Create file /config/helpers.js
Handlebars = require('handlebars');
module.exports = Handlebars.helpers;
Handlebars.registerHelper('stringify', function(obj) {
var json = {}, prop, tmp;
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
try {
tmp = JSON.stringify(obj[prop]);
json[prop] = obj[prop];
} catch (e) {
json[prop] = '[CAN NOT stringify]';
}
}
}
return JSON.stringify(json, null, 2);
});
In template I use {{stringify entry}}
Tested on Sails v0.12.13
I'm trying to compile a small LESS portion of code and mixing it with a bigger one already compiled in the page.
I thought there was some way to reuse the compiled less or maybe load it again, mix it with the newer code and then compile it mixed in the page.
I thought to load it in some way like the example below:
var runtime_less = '#bg:red; .selector { background-color:#bg; }';
var library_less = '#var:bla bla bla...';
var library_parser = new(less.Parser)({
paths: ['.', './lib'], // Specify search paths for #import directives
filename: 'css/full_library.less' // Specify a filename, for better error messages
});
frontsize_parser.parse('', function (e, tree) {
library_less = tree;
});
var runtime_parser = new(less.Parser)({});
runtime_parser.parse(library_less, function (e, tree) {
// this should be inside some load event
$("#container-style").text(library_less.toCSS() + ' ' + tree.toCSS());
});
Does exist some way to get the current page loaded LESS file and treat it in some way?
Or does exist some way to load LESS files and then mix the LESS data with a string with additional LESS code?
with t.less containing:
#color: lightgreen;
You can use the following code:
<link type="text/css" href="t.less" rel="stylesheet/less">
<script>
less = {
env: "development"
};
</script>
<script src="/less.js/dist/less.js"></script>
<script>
var tmp = less.parse;
less.parse = function(input,option,callback) {
tmp(input + ' h1 {color:#color;}',option,callback);
}
less.refreshStyles();
</script>
So. Once upon a time there were four magical creatures: asp.net mvc, require.js and angular. And one wise wizard decided to put them in the same house, and let for every single view of asp.net to have its own "code-behind" javascript file;
first he added to the _Layout.cshtml
<script data-main="/main" src="~/Scripts/require.js"></script>
and then he created main.js in the root:
require.config({
baseUrl: "/Scripts/",
paths: {
'jquery': 'jquery-1.9.1.min',
'jquery-ui': 'jquery-ui-1.10.2.custom.min',
'angular': 'angular.min',
'ng-grid': 'ng-grid-2.0.2.debug'
},
shim: {
'jquery': { exports: "$" },
'underscore': { exports: "_" },
'jquery-ui': ['jquery'],
},
});
// Standard Libs
require(['jquery','jquery-ui','underscore','angular']);
nothing fancy and magical yet. But then he created an html helper as such:
public static MvcHtmlString RequireJs(this HtmlHelper helper)
{
var controllerName = helper.ViewContext.RouteData.Values["Controller"].ToString(); // get the controllername
var viewName = Regex.Match((helper.ViewContext.View as RazorView).ViewPath, #"(?<=" + controllerName + #"\/)(.*)(?=\.cshtml)").Value; //get the ViewName - extract it from ViewPath by running regex - everything between controllerName +slash+.cshtml should be it;
// chek if file exists
var filename = helper.ViewContext.RequestContext.HttpContext.Request.MapPath("/Scripts/views/" + controllerName.ToLower() + "-" +
viewName.ToLower()+".js");
if (File.Exists(filename))
{
return helper.RequireJs(#"views/" + controllerName.ToLower() + "-" + viewName.ToLower());
}
return new MvcHtmlString("");
}
public static MvcHtmlString RequireJs(this HtmlHelper helper, string module)
{
var require = new StringBuilder();
require.AppendLine(" <script type=\"text/javascript\">");
require.AppendLine(" require(['Scripts/ngcommon'], function() {");
require.AppendLine(" require( [ \"" + module + "\"] );");
require.AppendLine(" });");
require.AppendLine(" </script>");
return new MvcHtmlString(require.ToString());
}
and then he could use it in _Layout.cshtml just like that:
#Html.RequireJs()
and if you were listening carefully to the story, you probably noticed that there was also Scripts/ngcommon.js file to manually bootstrap angular.js and have commonly used angular directives and services
require(['angular', 'jquery'], function() {
angular.module("common",[]).directive('blabla', function() {
return {
restrict: 'A',
scope: { value: "#blabla" },
link: function(scope, element, attrs) { }
}
});
//manually bootstrap it to html body
$(function(){
angular.bootstrap(document.getElementsByTagName('body'), ["common"]);
});
});
And here comes the magic: from now on if it was a javascript file in \Scripts\views named as controllerName-viewName.js as home-index.js for Home\Index.cshtml it would be automagically picked up by require.js and loaded. Beautiful isn't it?
But then the magician thought: What If I need to load something else (like ng-grid) and that something should not be injected into common angular module because not all the pages will be using it. Of course he could always manually bootstrap another module into a page element in each code-behind javascript where he needed, but he's not wise enough to find answer to the question:
Is it possible to inject some angular.js component (like ng-grid) directly into a controller, without having it as a part of the app module?
If I understand magician's idea right, then it is possible to go on by splitting your application into sub-modules being defined as a collection of components.
It will work if he sets up dependencies for main myApp module like:
var myApp = angular.module('myApp', ['Constants', 'Filters', 'Services', 'Directives', 'Controllers']);
myApp.Constants = angular.module('Constants', []);
myApp.Controllers = angular.module('Controllers', []);
myApp.Filters = angular.module('Filters', []);
myApp.Services = angular.module('Services', []);
myApp.Directives = angular.module('Directives', []);
Then each of sub-modules: Services etc. - can be extended with single component, like:
myApp.Controllers.controller('MyController', function () {});
myApp.Services.factory('myService', function () {});
myApp.Directives.directive('myDirective', function () {});
myApp.Filters.filter('myFilter', []);
myApp.Constants.constant('myConstant', []);
That way main application module is loaded with several sub-modules, but each structure is not important. It makes possible to include individual controllers, services, directives and filters on each page served from back-end - magician just needs to be sure that all needed dependencies are loaded.
DI is the magic key for having separate angular codebehind in MVC views.
You don't even need the requirejs at all, because angular is a dependency injector and module loader by nature, angular.bootstrap is the magic place to start.
So let wizard became more powerfull with the spell - $inject.
var TmplController = function($scope, $compile, $http... // any module itself
{
this.parts = ['legs','arms','head'];
$scope.dynamicPageTemplate = function($compile)
{
$compile('<div><p ng-repeat="each in parts">{{each}}</p></div>' )( $scope );
}
}
TmplController.$inject = ['$scope','$comple', '$http']; //try legs or head
refer complete annotated source of angular-scenario.js from https://github.com/angular/bower-angular-scenario, and you will find how to inject code with define manner helpers.
I saw there is somes questions related to mine (like this interesting one), but what I wonders is how to do it correctly, and I couldn't find it via the others questions or the RequireJS documentation.
I'm working on a quite heavy web application that will run in only one html page.
Before RequireJS, I used to do a lot of JS modules with public methods and connecting them via the on event on the Dom READY method, like this :
var DataList = function () {
this.base = arguments[0];
this.onUpdate = function (event) { ... }
}
$(function () {
var dataList = {}; DataList.apply(dataList, [$('#content')]);
$('table.main', dataList.base).on ('update', dataList.onUpdate);
});
With RequireJS, I can easily see that I can split DataList and all others classes like this on individual files, but what about the $(function () {}); part?
Can I still keep it this way, but instead of the DOM ready function of jQuery, I put the events on the main function() of the RequireJS, when my primary libs are loaded?
Or do I have to change the way I create JS "classes", to include a init function maybe, that will be called when I do a, for example :
require(['Datalist'], function(dataList) {
dataList.init($('#content'));
});
What annoys me the most is that since I have only one html file, I'm afraid the require() will have to load a huge list of files, I'd prefer it to load just libs that, them, would load sub libs required to work.
I don't know, the way of thinking with RequireJS lost me a bit :/
How would you do?
"Can I still keep it this way, but instead of the DOM ready function of jQuery, I put the events on the main function() of the RequireJS, when my primary libs are loaded?"
If you separate the functions or 'classes' into modules then you can use the RequireJS domReady function:
require(['module1'], function(module1) {
domReady(function(){
// Some code here ftw
})
});
The benefit here is the domReady function will allow downloading of the modules instantly but won't execute them until your DOM is ready to go.
"Or do I have to change the way I create JS "classes", to include a init function maybe, that will be called when I do a, for example"
You won't need to change the way you interact with your code this way, but you can probably improve it. In your example I would make DataList a module:
define(function(require) {
var $ = require('jquery');
var DataList = function () {
this.base = arguments[0];
};
DataList.prototype.onUpdate = function() {
};
return DataList;
});
require(['data-list'], function(DataList) {
var data = {};
// Call DataList with new and you won't need to set the context with apply
// otherwise it can be used exactly as your example
new DataList(data);
});
"What annoys me the most is that since I have only one html file, I'm afraid the require() will have to load a huge list of files, I'd prefer it to load just libs that, them, would load sub libs required to work."
Make your code as modular as you want/can and then use the optimiser to package it into one JS file.