sails.js use different layout with different js libraries - node.js

I'm creating my first nodejs/sails.js project, I want to use 3 different layout for 3 different occasions:
frontend_layout.ejs
admin_layout.ejs
mobile_layout.ejs
In frontend_layout.ejs, I want to load bootstrap.css, jquery.js and
bootstrap.js.
In admin_layout.ejs, I want to load bootstrap.css, angular.js and
ui-bootstrap-tpls.js(angular-ui js library).
In mobile_layout.ejs, I want to load ionic.css and ionic.bundle.js
I have created 3 folders in sailsProject/views/ which are admin_pages, mobile_pages and frontend_pages, the 3 layout.ejs files reside in each of these folders respectively, but no matter which layout I load, it always include all the css/js files in assets/js and assets/styles. I know I need to do something to pipeline.js, but how exactly? I'm not efficient at grunt, so I would really appreciate if anyone could point me which config files need to be modified and how... Thanks!

I wanted something very similar in my project, except I also wanted to take advantage of Sail's cool built-in ability to auto minimize/uglify javascript files for "sails lift --prod" in various layouts with different sets of javascript files. This answer only deals with the JS files, but you can make similar changes to support the same concept with your CSS files.
In my project I had 2 different layouts -- layout.ejs and layoutadmin.ejs. I created a new /assets/jsadmin folder which holds my admin javascript files. I left the sails existing /assets/js folder as-is to hold the javascript files for the public web pages.
My goal was for the /assets/js folder contents to be inserted between these tags (sails does this by default and these tags are used in the layout.ejs file):
<!--SCRIPTS-->
<!--SCRIPTS END-->
While the /assets/jsadmin folder contents was to be inserted between these tags (I made up these "custom" tag names and they are used in the layoutadmin.ejs file. I will add add support for this new tag in the rest of this answer):
<!--SCRIPTS_ADMIN-->
<!--SCRIPTS_ADMIN END-->
I created a full code sample demo of this here.
For development...
(sails lift), I modified so sails would populate my custom tags with the assets/jsadmin js files upon lifting.
I modified tasks/pipeline.js by adding a new variable called jsAdminFilesToInject which is very similar to the existing jsFilesToInject except it collects the js files from the jsAdmin folder.
var jsAdminFilesToInject = [
// Load sails.io before everything else
//'jsAdmin/dependencies/sails.io.js',
// Dependencies like jQuery, or Angular are brought in here
'jsAdmin/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'jsAdmin/**/*.js'
];
Note: I also had to export this new variable at the bottom of the pipeline.js file.
module.exports.jsAdminFilesToInject = jsAdminFilesToInject.map(function(path) {
return '.tmp/public/' + path;
});
I modified tasks/config/sails-linker.js by adding a new devJsAdmin task where it looks for tags and calls the new .jsAdminFilesToInject added in the pipeline.js file above.
devJsAdmin: {
options: {
startTag: '<!--SCRIPTS_ADMIN-->',
endTag: '<!--SCRIPTS_ADMIN END-->',
fileTmpl: '<script src="%s"></script>',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': require('../pipeline').jsAdminFilesToInject,
'views/**/*.html': require('../pipeline').jsAdminFilesToInject,
'views/**/*.ejs': require('../pipeline').jsAdminFilesToInject
}
},
I Added a new task step to the tasks/register/linkAssets.js file which calls the devJsAdmin added above.
'sails-linker:devJsAdmin',
To test, run sails in demo mode:
sails lift
Browse to http://localhost:1337/home - you will see it is using the layout.ejs template and viewing the source will show the following at the bottom (files pulled from js folder):
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/js/jquery-1.10.2.js"></script>
<!--SCRIPTS END-->
Browse to http://localhost:1337/admin - you will see it is using the layoutadmin.ejs template and viewing the source will show the following at the bottom of the source (files pulled from jsAdmin folder):
<!--SCRIPTS_ADMIN-->
<script src="/jsAdmin/dependencies/jquery-1.10.2.js"></script>
<script src="/jsAdmin/knockout-3.3.0.debug.js"></script>
<!--SCRIPTS_ADMIN END-->
For production...
(sails lift --prod), I wanted to do the same as development except I first wanted to concat and uglify the production javascript that goes in my new SCRIPTS_ADMIN tags.
I added a new jsAdmin section in the grunt tasks/config/concat.js file which pulls in the files from the previously added jsAdminFilesToInject in the pipeline.js to produce a concat/productionAdmin.js output file.
jsAdmin: {
src: require('../pipeline').jsAdminFilesToInject,
dest: '.tmp/public/concat/productionAdmin.js'
},
I added a new distAdmin section in the grunt tasks/config/uglify.js file which makes the concat/productionAdmin.js "ugly" by producing a new min/productionAdmin.min.js file.
distAdmin: {
src: ['.tmp/public/concat/productionAdmin.js'],
dest: '.tmp/public/min/productionAdmin.min.js'
}
I added a new prodJSAdmin section in the tasks/config/sails-linker.js file which adds the min/productionAdmin.min.js file between the SCRIPTS_ADMIN tags.
prodJsAdmin: {
options: {
startTag: '<!--SCRIPTS_ADMIN-->',
endTag: '<!--SCRIPTS_ADMIN END-->',
fileTmpl: '<script src="%s"></script>',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': ['.tmp/public/min/productionAdmin.min.js'],
'views/**/*.html': ['.tmp/public/min/productionAdmin.min.js'],
'views/**/*.ejs': ['.tmp/public/min/productionAdmin.min.js']
}
},
Finally, I called this new prodJSAdmin from the prod grunt task by adding a line in the prod.js file.
'sails-linker:prodJsAdmin',
Run sails in production mode:
sails lift --prod
Browse to http://localhost:1337/home - you will see it is using the layout template and viewing the source will show the following at the bottom (using production.min.js):
<!--SCRIPTS-->
<script src="/min/production.min.js"></script>
<!--SCRIPTS END-->
Browse to http://localhost:1337/admin - you will see it is using the layoutadmin.ejs template and viewing the source will show the following at the bottom of the source (using productionAdmin.min.js):
<!--SCRIPTS_ADMIN-->
<script src="/min/productionAdmin.min.js"></script>
<!--SCRIPTS_ADMIN END-->

By default, Sails automatically insert all your css files (assets/styles) into tags between STYLES and STYLES END and js files (assets/js) into tags between SCRIPTS and SCRIPTS END.
<!--STYLES-->
<!--STYLES END-->
.
.
.
<!--SCRIPTS-->
<!--SCRIPTS END-->
This is set in pipeline.js file. By default it has set to get all css files from assets/styles. You can find it in cssFilesToInject section.
'styles/**/*.css'
You can change it as you wish. you can comment or delete it simply. (keep in mind if you want to put some css files common to every layout you can put them in here.)
Same for the js files. By default it has set to get all js files from assets/js. You can find it in jsFilesToInject section. Remove or add js files according to your requirement. You can find more information about grunt globbing patterns in here which helps to understand filtering pattern.
So easiest thing you can do now is put your layout specific files out side those tags(STYLES and SCRIPTS)
For example look following code sample,
<!--STYLES-->
<!--STYLES END-->
<!--STYLES SPECIFIC TO THIS LAYOUT-->
<link rel="stylesheet" href="/styles/some_layout_specific.css">

Related

How to call a function (exported from a node module) by a button click event from a dom?

please have a look on this image which contains the snapshots of the
(a) directory structure, (b) main.js, (c) myModule.js, (d) index.html.
In case the link doesn't work, the directory structure is:
- root/
- dist/
bundle.js
+ node_modules/
- src/
main.js
myModule.js
index.html
webpack.config.js
I have a function myFunction exported from myModule.js which somehow generates and plays a MIDI note. There also lives a button in the index.html. Now, when the user clicks the button (while the page being viewed in the browser), I want to call myFunction(). How can I do that?
I used require in main.js to include the exports of myModule.js and used webpack to make a bundle.js (please see the image). But then I came to know that node.js doesn't have a native dom.
I found out that, it can be done using libraries/frameworks (e.g. React, etc). But is there anyway of doing it without using the libraries?
In main.js replace the second line with:
document.addEventListener('DOMContentLoaded', function()
{
document
.getElementById('myButton')
.addEventListener('click', MyModule.myFunction());
});
And then bundle

Renaming files with MD5

Was going through gulp packages on the npm website and came across this package called gulp-rename-md5. Is there a scenario where renaming a file using MD5 is useful and why?
I've used a similar tool for cache busting (called gulp-freeze which adds an MD5 hash of the file contents to the filename).
When you update a CSS or JS file you want users to get the latest version of that file when they visit your site. If your file is named "app.min.js" and you update it, their browsers might not pull down the latest file. If you're using a CDN even clearing the browser cache probably won't request the new file.
I've used gulp-freeze with gulp-filenames (to store the name of the cache busted file) and gulp-html-replace (to actually update the <link /> or <script /> tags with the name of this cache busted file in the html). It's really handy.
CODE SAMPLE
This will get your files, use gulp-freeze to build the hash, use gulp-filenames to store the name of that file, then write that to the html with gulp-html-replace. This is tested and working
var gulp = require("gulp"),
runSequence = require("run-sequence"),
$ = require("gulp-load-plugins")();
gulp.task("build", () => {
runSequence("js", "write-to-view");
});
gulp.task("js", () => {
return gulp
.src("./js/**/*.js")
.pipe($.freeze())
.pipe($.filenames("my-javascript"))
.pipe(gulp.dest("./"));
});
gulp.task("write-to-view", () => {
return gulp
.src("index.html")
.pipe(
$.htmlReplace(
{
js: $.filenames.get("my-javascript")
},
{ keepBlockTags: true }
)
)
.pipe(gulp.dest("./"));
});
EDIT
I should add that index.html just needs these comments so gulp-html-replace knows where to write the <script /> element
<!--build:js-->
<!-- endbuild -->
One of advantages is that you can setup your app to cache files with MD5 sum in their name (e.g. mystyle.a345fe.css) for long time (several months) because you know that this file will never be modified. This will save you some traffic and your web will be faster for returning visitors.

How to use Magento2 with RequireJS?

I'm just migrating from Magento 1.x to Magento 2.x. I found that Magento2 uses RequireJS for handling JavaScript files. So I learnt what RequireJS is, and how to use it.
I found that most of the examples uses data-main="main" to define the configuration file.
In Magento2's default_head_blocks.xml file, I found the script tag like this:
<script src="requirejs/require.js"/>
Here they did not specify any data-main.
These are my questions:
How Magento2/RequireJS knows which JS should be loaded for configuration? (I found requirejs-config.js for this in multiple places)
By default Magento2 loads lots of JS (more than 20), how can I limit them?
I could not find enough documentation on this.
The best place to get all your answers for Magento 2 JS development is the Magento 2 docs, it really is a useful resource on this. http://devdocs.magento.com/guides/v2.0/javascript-dev-guide/javascript/js_init.html explains in detail about component initialisation.
To answer your two questions above -
Q.1. How Magento2/RequireJS knows which JS should be loaded for
configuration? (I found requirejs-config.js for this in multiple
places)
In each Magento 2 module there is a requirejs-config.js file to load all that modules configuration. i.e.
var config = {
map: {
'*': {
compareItems: 'Magento_Catalog/js/compare',
compareList: 'Magento_Catalog/js/list',
relatedProducts: 'Magento_Catalog/js/related-products',
upsellProducts: 'Magento_Catalog/js/upsell-products',
productListToolbarForm: 'Magento_Catalog/js/product/list/toolbar',
catalogGallery: 'Magento_Catalog/js/gallery',
priceBox: 'Magento_Catalog/js/price-box',
priceOptionDate: 'Magento_Catalog/js/price-option-date',
priceOptionFile: 'Magento_Catalog/js/price-option-file',
priceOptions: 'Magento_Catalog/js/price-options',
priceUtils: 'Magento_Catalog/js/price-utils',
catalogAddToCart: 'Magento_Catalog/js/catalog-add-to-cart'
}
}
};
This is telling requirejs where all the required JavaScript files are located.
There are multiple way to tell Magento when to use your JS file -
data-mage-init on a HTML element. e.g. <div class="block upsell" data-mage-init="{"upsellProducts":{}}" data-limit="0" data-shuffle="0">
script tag on the page e.g
<script type="text/x-magento-init">
{
"[data-role=tocart-form], .form.map.checkout": {
"catalogAddToCart": {}
}
}
</script>
within a JS file e.g. $('.yourSelector').yourPlugin();
Q.2. By default Magento2 loads lot's of JS (more than 20), how can I
limit them?
The sheer number of JS files that are loaded as a result of multiple modules is one of the downsides, however, with the correct usage of full page caching with a reverse proxy like Varnish the performance reduction is negligible, even in a development server.

How does the text! plugin use the baseUrl?

I'm having an issue getting the text! plugin to work in my requirejs site. It's always including lib/ in the request url, however all of the other files (not using text!) are being successfully found and loaded. Here is my directory structure:
WebContent/
|--backbone/
|--Bunch of folders and files
|--config/
|--config.js
|--lib/
|--jquery.js
|--text.js
|--require.js
|--index.html
my index.html file is:
<body>
<div id="siteLayoutContainer"></div>
<script data-main='config/config' src="lib/require.js"></script>
</body>
The config file is:
requirejs.config({
baseUrl: './',
paths: {
jquery: 'lib/jquery.js',
backbone: 'lib/backbone.js',
text: 'lib/text',
application: 'backbone/application'
},
text: {
env: 'xhr'
}
});
require(['application'], function(App) {
App.start();
});
I'm using the text! plugin like so:
define([
'jquery',
'text!backbone/templates/SomeTemplate.html'
], function(jQuery, NotFoundHtml) {
//Some code here
}
So, in the above script, the url being used for the template is:
http://localhost/lib/backbone/templates/SomeTemplate.html
and I am expecting it to be:
http://localhost/backbone/templates/SomeTemplate.html
I've tried the following:
Moving the text.js and require.js files out into the WebContent
directory but I get the same results. Also something interesting is
if I put a space after text! and then the path, that works fine and
doesn't include the lib/ directory in the request to get the html
template. However the optimizer includes the space and can't find the
template.
Not defining a baseUrl - same results.
Moved the
require config.js content into index.html in it's own script tag that runs
before the require.js script tag - same results.
Getting rid of the the text options in the config file
Oh yeah, forgot I've also tried 'text!../backbone/templates/SomeTemplate.html - same results
So I'm stuck and can't figure out what I'm missing. I'm obviously not understanding how the text! plugin uses the baseUrl or how it determines the url it's going to use to fetch the defined file.
After your edits to your question, it now contains all the information to diagnose the problem. As you guessed in one of your comments, the issue is indeed that this path:
backbone: 'lib/backbone.js',
is throwing off the resolution of the template you give to the text plugin. When the text plugin loads what you give to it, it takes the path after the ! symbol and treats it as if it were a module name, and it goes through the module resolution process. The way module resolution works is that it checks if there is a prefix that matches any of the keys in paths and will change the prefix with the value associated with the key, which gives the result you obtained. One way to fix the issue would be to add this to your paths configuration:
"backbone/templates": "backbone/templates"
This will make it so that anything you request under backbone/templates won't get messed up by the backbone path.
Note: it is preferable to avoid putting extensions in module names so you should remove it from the values you have for jQuery and Backbone.

Reduce HTTP requests bundling bower_components together?

I'm developing a web application and I'm using about 20 bower_components (bootstrap, jquery, some jquery plugin like cookie and serializejson, mousewheel, mustache, underscore etc).
All these plugins are quite small (except for bootstrap and jquery), but are loaded with a dedicated "" tag.
This makes my page asks for 20 micro javascript files and this makes the page load slowly.
I'd like to bundle all these scripts together and load the bundle...
I've tried with gulp-bower-src but it just help me to uglify them and move them in a "lib" folder... Can't find a way to compress them all together.
This is my gulp task atm
var filter = gulpFilter("**/*.js", "!**/*.min.js");
gulp.task("default", function () {
bowerSrc()
.pipe(filter)
.pipe(uglify())
.pipe(filter.restore())
.pipe(gulp.dest(__dirname + "/public/lib"));
});
How can I do?
You can use gulp-concat to bundle all these micro files into one asset.
var concat = require('gulp-concat');
var filter = gulpFilter("**/*.js", "!**/*.min.js");
gulp.task("default", function () {
bowerSrc()
.pipe(filter)
.pipe(uglify())
.pipe(concat('bundle.js'))
.pipe(filter.restore())
.pipe(gulp.dest(__dirname + "/public/lib"));
});
This will concat all filtered and uglified files as bundle.js into directory public/lib. Keep in mind that when concatenating these files the order of files matters. I guess that gulp-bower-src does not order scripts according to a dependency graph (I found nothing in the documentation) so it might require you to select the bower files by hand in the right order.
A manual ordering/selecting of the bower components could be done by substituting the bowerSrc() by a line somehow like this
gulp.src(['./bower_components/jquery/jquery.js', './bower_components/jquery-ui/jquery-ui.js', './bower_components/jquery-swift-color-picker/color.js']);
This may seem a little clumsy and it is but order matters.

Resources