I'm using r.js to build a production script for my Require JS application and I'm looking for a way to either use an alternate compression library or change the defaults used. I want whitespace to be removed but for variable names to remain the same.
I have a particular requirement to retain variable names as they are and not have them altered. The need for constant variable names introduces a little 'code smell' but it makes the application's configuration file more robust against non-expert editors - so please try to avoid suggesting a design change here.
I currently have r.js configured to not optimise the JavaScript at all, which means not only are variable names retained but also whitespace. The relevant piece from gruntfile.js is provided below.
Can anyone suggest a way to compress whitespace but not change variable names in an r.js build?
english: {
options: {
baseUrl: "js",
mainConfigFile: "js/app-en.js",
name: "app-en",
out: "js/dist/<%= pkg.name %>-en.js",
optimize: "none"
}
}
The r.js optimizer has settings you can use to control how the minifier operates. The default minifier used is UglifyJS. The uglifyjs option tells r.js how to invoke it. Given the settings you've shown, removing optimize: "none" and adding uglify: { no_mangle: true } is what is needed:
english: {
options: {
baseUrl: "js",
mainConfigFile: "js/app-en.js",
name: "app-en",
out: "js/dist/<%= pkg.name %>-en.js",
uglify: {
no_mangle: true
},
}
}
The whole set of settings that UglifyJS takes is documented here. If you ever need or want to switch to UglifyJS2 or Closure, r.js has uglify2 and closure settings that you can use to set their options.
For Uglify2, the setting to prevent mangling would be:
uglify2: {
mangle: false
}
With Closure, I believe you'd want:
closure: {
CompilationLevel: 'WHITESPACE_ONLY',
},
Related
I am using r.js with uglify to minify and concatenate my scripts. I am getting some errors on a production site where the stack trace returned is unintelligible. I would like to temporarily turn off the mangling of function names (variable names are fine) and am having trouble working out how to do this as r.js wraps the configuration options that are passed to uglify.js
The uglify config section in my r,js build config looks like this
uglify: {
beautify: true,
indent_start: 0,
indent_level: 1,
}
I would like to add the
-nmf or --no-mangle-functions – in case you want to mangle variable names, but not touch function names. (from here)
If i add the line
uglify: {
beautify: true,
indent_start: 0,
indent_level: 1,
'--no-mangle-functions': true
}
It does nothing, as does 'no-mangle-functions': true.
How do I pass this option to uglify?
Trying to make uglified/mangled code readable kind of defeats it's purpose in the first place.
Probably, what you are after are source maps.
To generate source maps in Uglify just add these options:
uglify: {
options: {
sourceMap: 'build.min.map',
sourceMappingURL: 'build.min.map'
}
}
Map filename must mirror the final javascript filename:
uglified.js <=> uglified.map
From what i can see in the source code of r.js there is no direct differentiation between functions and variable names. But there is an option called no_functions which is actually passed to the uglify section where the default value is false
Passing of options:
https://github.com/jrburke/r.js/blob/master/dist/r.js#L25067
Defaulting no_functionsto false:
https://github.com/jrburke/r.js/blob/master/dist/r.js#L11492
I cannot test it right now, so i am only guessing. Maybe you can try this option
I use grunt to convert all my less files into css files,using this:
less: {
development: {
files: {
"css/*.css": "less/*.less"
}
}
}
This worked on version 0.3.0, but now that I have upgraded to v0.4.0 it doesn't work anymore.
The following code (not using * in the destination) works on both versions, so the problem is with the star on the destination file.
less: {
development: {
files: {
"css/test.css": "less/*.less"
}
}
}
Any idea ?
This isn't a bug. Grunt no longer supports globbing in dest using that configuration. However, you can use the "files array" format, like this:
files: [
{
expand: true,
cwd: 'src',
src: ['*.less'],
dest: 'assets/css/',
ext: '.css'
}
]
Also, if you use a library like Bootstrap and you want to build each LESS file (component) into a separate, individual CSS file, it's not very easy to accomplish "out of the box". The reason is that each LESS file would need to have its own #import statements for variables.less and mixins.less (and a couple of others like forms.less and navbar.less, since they are referenced in other files).
To make this really easy, try the Grunt plugin, assemble-less (disclaimer: I'm one of the maintainers of the project, and I'm also on the core team for less.js). assemble-less is a fork of grunt-contrib-less by Tyler Kellen, but it adds some experimental features that will accomplish what you need (if you want stability, please stick with grunt-contrib-less). For example:
// Project configuration.
grunt.initConfig({
less: {
// Compile all targeted LESS files individually
components: {
options: {
imports: {
// Use the new "reference" directive, e.g.
// #import (reference) "variables.less";
reference: [
"bootstrap/mixins.less",
"bootstrap/variables.less"
]
}
},
files: [
{
expand: true,
cwd: 'bootstrap/less',
// Compile each LESS component excluding "bootstrap.less",
// "mixins.less" and "variables.less"
src: ['*.less', '!{boot,var,mix}*.less'],
dest: 'assets/css/',
ext: '.css'
}
]
}
}
...
}
The imports feature essentially prepends the specified #import statements onto the source files. The reference option allows you to "reference" other less files while only outputting styles that are specifically referenced via mixins or :extend. You might need to reference a few more files than shown here, since Bootstrap cross-references styles from other components, like forms.less, buttons.less, etc. (See the Gruntfile in assemble-less for examples.)
So after running the assemble-less task with the configuration in the example above, the assets/css folder would have:
alerts.css
badges.css
breadcrumbs.css
button-groups.css
buttons.css
carousel.css
close.css
code.css
component-animations.css
dropdowns.css
forms.css
glyphicons.css
grid.css
input-groups.css
jumbotron.css
labels.css
list-group.css
media.css
modals.css
navbar.css
navs.css
normalize.css
pager.css
pagination.css
panels.css
popovers.css
print.css
progress-bars.css
responsive-utilities.css
scaffolding.css
tables.css
theme.css
thumbnails.css
tooltip.css
type.css
utilities.css
wells.css
There are other features that should help you with this, but the imports feature is super powerful since it allows you to add directives directly to the Gruntfile.
Is it possible to generate dynamic filenames outside the built-in grunt tasks (e.g. concat or min)? I tried to use something like <config:concat.dist.dest> or <%= dirs.dest %> as it is described in the docs. But this never gets interpreted / compiled, it just writes out the string.
Update:
That's what I tried based on jakub.g's answer. My grunt.js looks like this:
// ... grunt file contents
jquery: {
exclude: [],
version: '1.8.3',
dest: '../dist/js/jquery-' + grunt.task.directive('<config:jquery.version>') + '.js',
minify: false
}, // ... even more grunt file contents
grunt.task.directive('<config:jquery.version>') returns null. So the filename was named jquery-null.js.
I then tried grunt.template.process('<%= grunt.jquery.version %>') and grunt.config.process('<%= grunt.jquery.version %>'), but none of them worked.
This is hidden under the hood of Grunt magic in the built-in tasks and in fact not documented clear enough.
You need to use sth like grunt.task.directive(dest) to evaluate things like <config:..>. in a custom task.
For <%= foo %>, have a look at Grunt templates.
Furthermore, wildcards like * and ** and also not expanded by default, if you want to use them in custom tasks, you may use grunt.file.expandFiles().
I'm currently setting up a automated build script (with gruntjs) for a require.js driven project . Therefor I would like to run jslint/jshint on all required files before concatenating and minifying it with r.js. Since the js folder contains a lot of development files I don't want to lint, I can't just pass js/**/*.js to JSLint. My first thought was to run r.js with optimizer: 'none', lint the concatenated file and then minify it, but this is not an options for two reasons. First it will include vendor libs I don't want to lint and second finding the line with the error, find it's class, find the appropriate js-file in the dev folder, fix it there, run r.js again and finally lint it again, is way to much hassle for our workflow. So I'm looking for a possibility to hook up the linting into the r.js optimizer process or at least get a list of the requirejs dependency tree in some way, that I can parse and pass it to lint. Or any solution practicable for an automated process, you'll come up with.
Lint first, compile later. Just be specific about the files you want to lint and use the ! prefix to ignore specific files:
grunt.initConfig({
lint: {
// Specify which files to lint and which to ignore
all: ['js/src/*.js', '!js/src/notthisfile.js']
},
requirejs: {
compile: {
options: {
baseUrl: 'js/src',
name: 'project',
out: 'js/scripts.js'
}
}
}
});
// Load the grunt-contrib-requirejs module.
// Do `npm install grunt-contrib-requirejs` first
grunt.loadNpmTasks('grunt-contrib-requirejs');
// Our default task (just running grunt) will
// lint first then compile
grunt.registerTask('default', ['lint', 'requirejs']);
I prefer not overriding r.js's methods, or you might create an unwanted dependency on a specific version (you'll need to update your code should r.js change)
This is the code I use for the same purpose, making use of require's onBuildRead function and the fact that objects in javascript are passed by reference. I make sure I run the require build first, then lint the js file sources.
The downside is that you'll lint after build complete. For my setup that is not a problem.
module.exports = function(grunt) {
var jsHintOptions = {
options: {
curly: true,
eqeqeq: true,
eqnull: true,
browser: true,
globals: {
jQuery: true
}
},
all: [] // <--- note this is empty! We'll fill it up as we read require dependencies
};
var requirejsOptions = {
compile: {
options: {
paths: {
"jquery": "empty:"
},
baseUrl: "./",
name: "src/mypackage/main",
mainConfigFile: "src/mypackage/main.js",
out: 'build/mypackage/main.js',
onBuildRead: function (moduleName, path, contents) {
jsHintOptions.all.push(path); // <-- here we populate the jshint path array
return contents;
}
}
}
};
grunt.initConfig({
pkg: grunt.file.readJSON('packages/mypackage.package.json'),
requirejs: requirejsOptions,
jshint: jsHintOptions
});
// load plugin that enabled requirejs
grunt.loadNpmTasks('grunt-contrib-requirejs');
// load code quality tool
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['requirejs', 'jshint']); // <-- make sure your run jshint AFTER require
};
This answer sort of bypasses Grunt, but it should work for what you want to do. The way I would do it is look at r.js and try to override a function that receives the path to the various modules being loaded, intercept the module name, and lint the files while r.js is loading and compiling the modules. I've done it like so:
var requirejs = require('requirejs');
var options = {/*r.js options as JSON*/};
var oldNewContext = requirejs.s.newContext;
requirejs.s.newContext = function(){
var context = oldNewContext.apply(this, arguments);
var oldLoad = context.Module.prototype.load;
context.Module.prototype.load = function(){
var module = oldLoad.apply(this, arguments);
if(/\.js$/.test(this.map.url) && !/^empty:/.test(this.map.url))
console.log(this.map.url);
return module;
}
return context;
}
requirejs.optimize(options)
Then when you run requirejs.optimize on your modules, you should get all the non-empty JavaScript urls logged to the console. Instead of logging them to the console, you could use the urls to lint the files.
Instead of using the lint task, install, load, and set up grunt-contrib-jshint. It has an ignores option to ignore specific files or file path patterns.
Here's my task:
jshint: {
options: {
// options here to override JSHint defaults
boss : true, // Suppress warnings about assignments where comparisons are expected
browser : true, // Define globals exposed by modern browsers (`document`, `navigator`)
curly : false, // Require curly braces around blocks
devel : false, // Define `console`, `alert`, etc. (poor-man's debugging)
eqeqeq : false, // Prohibit the use of `==` and `!=` in favor of `===` and `!==`
"-W041" : false, // Prohibit use of `== ''` comparisons
eqnull : true, // Suppress warnings about `== null` comparisons
immed : true, // Prohibit the use of immediate function invocations w/o wrapping in parentheses
latedef : true, // Prohibit the use of a var before it's defined
laxbreak: true, // Suppress warnings about possibly unsafe line breaks
newcap : true, // Require you to capitalize names of constructor functions
noarg : true, // Prohibit the use of `arguments.caller` and `arguments.callee`
shadow : true, // Suppress warnings about var shadowing (declaring a var that's declared somewhere in outer scope)
sub : true, // Suppress warnings about using `[]` notation, e.g. `person['name']` vs. `person.name`
trailing: true, // Trailing whitespace = error
undef : false, // Prohibit the use of explicitly undeclared variables
unused : false, // Warn when you define and never use your variables
white : false, // Check JS against Douglas Crawford's coding style
jquery : true, // Define globals exposed by jQuery
// Define global functions/libraries/etc.
globals : {
amplify : true
},
ignores: [
'src/app/templates/template.js',
'src/scripts/plugins/text.min.js'
]
},
gruntfile: {
src: 'Gruntfile.js'
},
app: {
src: 'src/app/**/*.js'
},
scripts: {
src: 'src/scripts/**/*.js'
}
}
In our Dojo system, we have something like the following specified in our dojoConfig:
packages: [{
name: "myWidgets",
location: "/js/libs/widgets"
}]
So that in our require statements, all we have to do is something like:
require(["myWidgets/myCalendarWidget"....
The problem is when I run the build, this dojoConfig is not available and I get numerous missing dependency errors because 'myWidgets' isn't defined according to the build profile.
Now, I've tried adding a package block to the build profile also, but the end result of that is to create an actual 'myWidgets' package, which I don't want.
So, is there any way to make the build see the definition of the 'myWidgets' alias, yet have the end result of the build output still mirror the regular file structure (i.e. /js/libs/widgets)? I tried to define these path aliases in the defaultConfig element in the build profile and that doesn't work either.
If you are using a profile, you can specify the packages in the profile
/util/buildscripts:./build.sh profile=../../../myProfile.js
http://dojotoolkit.org/reference-guide/1.8/build/buildSystem.html#profile-basics
You can also specify a javascript file that holds the dojoConfig
/util/buildscripts:./build.sh --dojoConfig ../build/examples/dojoConfig.js
http://dojotoolkit.org/reference-guide/1.8/build/buildSystem.html#using-a-package-configuration
Answer to your comment. The path is relative from where dojo.js is.
var dojoConfig = {
parseOnLoad: true,
isDebug: true,
locale: 'en-us',
paths: {
"evf": "../../evf"
}
};
My directory structure looks like
js/dojo-1.8.0
dijit
dojo <-- contains dojo.js
dojox
util
js/evf
myCustomWidget.js