Customize Bootstrap within MEAN.JS
Currently, MEAN.JS ships with Bootstrap but does not support extending it with LESS. To add additional styles, we need to add CSS files. To customize the default theme, we need to overwrite exiting styles with a new CSS. This layered approach is impractical for several reasons. First, we need write CSS instead of LESS. Second, we don't access to all the variables and mixins provided by Bootstrap.
I would much rather generate a single CSS file combining Bootstrap, my customizations, and additional styles together. This file is placed in public/application.min.css. Let's see how we add LESS compilation and remove the existing auto inclusion of CSS files scattered in various AngularJS modules.
Remove CSS
Edit config/env/all.js
:
- Remove from
assets.lib.css
arraypublic/lib/bootstrap/dist/css/bootstrap.css
andpublic/lib/bootstrap/dist/css/bootstrap-theme.css
. Make sure to leave an empty array (don't remove the css array) - Remove from
assets.css
the first elementpublic/modules/**/css/*.css
. We don't write CSS but LESS. Addpublic/application.min.css
to this array. It will be auto included byapp/views/templates/layout.server.view.html
- Change
assets.css
topublic/application.min.css
'use strict';
module.exports = {
app: {
title: 'my first MEAN.JS app',
description: 'Try out MEAN.JS',
keywords: 'nodejs, meanjs, trial'
},
port: process.env.PORT || 3000,
templateEngine: 'swig',
sessionSecret: 'MEAN',
sessionCollection: 'sessions',
assets: {
lib: {
js: [
'public/lib/angular/angular.js',
'public/lib/angular-resource/angular-resource.js',
'public/lib/angular-cookies/angular-cookies.js',
'public/lib/angular-animate/angular-animate.js',
'public/lib/angular-touch/angular-touch.js',
'public/lib/angular-sanitize/angular-sanitize.js',
'public/lib/angular-ui-router/release/angular-ui-router.js',
'public/lib/angular-ui-utils/ui-utils.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.js'
],
css: [],
},
css: [
'public/application.min.css',
],
js: [
'public/config.js',
'public/application.js',
'public/modules/*/*.js',
'public/modules/*/*[!tests]*/*.js'
],
tests: [
'public/lib/angular-mocks/angular-mocks.js',
'public/modules/*/tests/*.js'
]
}
};
Edit config/env/production.js
and remove assets.lib.css
and assets.css
entries.
Combine LESS Files Into One
- Copy the file
public/lib/bootstrap/less/bootstrap.less
topublic/less/application.less
and fix the import path of less files. - Add a file
public/less/variables.less
to overwrite Bootstrap variables and add any new variables. Import it intopublic/less/application.less
by inserting it right after the import of bootstrap variables. - Add a file
public/less/mixins.less
to overwrite Bootstrap mixins and define your own mixins. Import it intopublic/less/application.less
by inserting it right after the import of bootstrap mixins. - Include any other LESS files you need to create. Files defining styles for the entire application would go in
public/less
. Files with style for specific modules go in their respective less folderspublic/modules/xyz/less
The public/less/application.less
should look like this:
// Core variables and mixins
@import "../lib/bootstrap/less/variables.less";
// -- Overwrite bootstrap and additional variables
@import "variables.less";
@import "../lib/bootstrap/less/mixins.less";
// -- Overwrite bootstrap and additional mixins
@import "mixins.less";
// Reset and dependencies
@import "../lib/bootstrap/less/normalize.less";
@import "../lib/bootstrap/less/print.less";
@import "../lib/bootstrap/less/glyphicons.less";
// Core CSS
@import "../lib/bootstrap/less/scaffolding.less";
@import "../lib/bootstrap/less/type.less";
@import "../lib/bootstrap/less/code.less";
@import "../lib/bootstrap/less/grid.less";
@import "../lib/bootstrap/less/tables.less";
@import "../lib/bootstrap/less/forms.less";
@import "../lib/bootstrap/less/buttons.less";
// Components
@import "../lib/bootstrap/less/component-animations.less";
@import "../lib/bootstrap/less/dropdowns.less";
@import "../lib/bootstrap/less/button-groups.less";
@import "../lib/bootstrap/less/input-groups.less";
@import "../lib/bootstrap/less/navs.less";
@import "../lib/bootstrap/less/navbar.less";
@import "../lib/bootstrap/less/breadcrumbs.less";
@import "../lib/bootstrap/less/pagination.less";
@import "../lib/bootstrap/less/pager.less";
@import "../lib/bootstrap/less/labels.less";
@import "../lib/bootstrap/less/badges.less";
@import "../lib/bootstrap/less/jumbotron.less";
@import "../lib/bootstrap/less/thumbnails.less";
@import "../lib/bootstrap/less/alerts.less";
@import "../lib/bootstrap/less/progress-bars.less";
@import "../lib/bootstrap/less/media.less";
@import "../lib/bootstrap/less/list-group.less";
@import "../lib/bootstrap/less/panels.less";
@import "../lib/bootstrap/less/responsive-embed.less";
@import "../lib/bootstrap/less/wells.less";
@import "../lib/bootstrap/less/close.less";
// Components w/ JavaScript
@import "../lib/bootstrap/less/modals.less";
@import "../lib/bootstrap/less/tooltip.less";
@import "../lib/bootstrap/less/popovers.less";
@import "../lib/bootstrap/less/carousel.less";
// Utility classes
@import "../lib/bootstrap/less/utilities.less";
@import "../lib/bootstrap/less/responsive-utilities.less";
// these are your LESS files
@import "style.less"
@import "../modules/one/less/style.less"
@import "../modules/two/less/style.less"
Add LESS Support
Install the grunt task to compile LESS files
% npm install grunt-contrib-less --save
Tell grunt to compile our LESS file by adding this block into gruntfile.js
in the grunt.initConfig
options
less: {
production: {
options: {
paths: ['public/less'],
cleancss: true,
compress: true
},
files: {
'public/application.min.css': 'public/less/application.less'
}
},
development: {
options: {
sourceMap: true,
ieCompat:true,
dumpLineNumbers:true
},
files: {
'public/application.min.css': 'public/less/application.less'
}
}
},
Remove the uglify
and cssmin
sections, they are no longer needed. Less do this while compiling the combined file.
In the build
task remove cssmin
and uglify
and add less
grunt.registerTask('build', ['lint', 'loadConfig', 'ngmin', 'less']);
Change ngAnnotate.production.files
to 'public/application.js': '<%= applicationJavaScriptFiles %>'
Then load the less task installed earlier
grunt.loadNpmTasks('grunt-contrib-less');
Now, go to your shell and compile LESS by running grunt build
or grunt less
Auto reload
Once you built application.min.css
, you can have the browser auto reload it by editing gruntfile.js
and change the watchFiles
options to
clientCSS: ['public/application.min.css', 'public/modules/**/*.css'],
Written by Quoc Vu
Related protips
8 Responses
oh one more thing would be that developers may not want to do the combination of override files in the Application.min.css file so they can implement less maps. This allows the ability to see less line numbers during browser inspection when the maps are added to the grunt.js.
e.g. -
less: {
development: {
options: {
sourceMap: true,
ieCompat:true,
dumpLineNumbers:true
},
files: {
// target.css file: source.less file
}
}
},
Thanks for the suggestion. I have updated the post accordingly.
Cheers
wth about css.map in public/ dir?
i see an error about css.map in my console
Thanks for this article.
I would like to point that You should also remove dependancy for: "bootstrap": "~3" in bower.json since there is new dependancy: "bootstrap-sass-official": "~3.3.3"
There is a problem with source mapping, i.e. it gives 404 - found because in application.min.css there is this URL:
/*# sourceMappingURL=public/application.min.css.map */
To fix this, add a configuration to "less.development.options" in "grunt.initConfig":
sourceMapURL: 'application.min.css.map'
This should fix the 404 error
Another HUGE problem is that public/application.js file, who holds AngularJS initialization code and which is created by MeanJS, gets overwritten by ngAnnotate task. This prevents angular from properly initializing!
Solution is:
- move code from application.js a new file: public/angular-bootstrap.js;
- in config/env/all.js, change (in "assets.js" path) 'public.application.js' to 'public/angular-bootstrap.js'
Last one... I hope!
There is a problem if running in production, i.e.:
TypeError: Cannot call method 'concat' of undefined
at /config/config.js:76
This is because we removed "assets.lib.css" and "assets.css" from production.js
To fix this we need to modify config/config.js:
module.exports = _.extend(
require('./env/all'),
require('./env/' + process.env.NODE_ENV) || {}
);
must be changed to:
module.exports = _.defaults(
require('./env/all'),
require('./env/' + process.env.NODE_ENV) || {}
);
So undefined properties are kept.
Thanks a lot megadix.
It would be best if you could fork the project and issue pull requests for the proposed fixes. That would ensure we all get that stuff in future releases.