Minifying assets in Apache Cordova helps you to reduce the size of your apk or aab file, and also allows you to uglify any sensible information you may have therein. The minification should be done to all assets, specifically html
, css
and js
files after cordova prepare
, that is, before cordova compile
.
The files to be minified should obey the following pattern, inside your www
dir:
./**/*.html # all .html files
+-- css/**/*.css # all .css files inside css/ dir, except .min.css files
+-- js/**/*.js # all .js files inside js/ dir, except .min.js files
Install dependencies
npm i -D async walk uglify-js uglifycss html-minifier
Add a hook to your config.xml
, let's name it minifyFiles.js
. In your config.xml
add this line
<hook src="hooks/minifyFiles.js" type="after_prepare"/>
This works on every platform, thus do not include this line inside any specific platform. Pay attention that hooks are run in order by which they appear in config.xml
.
Create a file hooks/minifyFiles.js
at the root directory (or whatever path you defined above in config.xml
) with this content:
/* NodeJS script that minifies files stored in pltaforms/ after cordova prepare, and for every added platform
It minifies all js, css and html files that will be compiled to build apk or aab file.
This script is asynchronous for every file in any platform, to improve performance.
*/
// node/npm includes
const fs = require('fs')
const path = require('path')
const async = require('async')
const walk = require('walk')
// minification tools
const UglifyJS = require('uglify-js')
const uglifycss = require('uglifycss')
const minifyHTML = require('html-minifier').minify
const twoSpaces = ' '
module.exports = function (context) {
console.log(`${context.hook} : ${path.relative(context.opts.projectRoot, context.scriptLocation)}`)
var projectRoot = context.opts.projectRoot
var platforms = []
for (var i = 0; i < context.opts.platforms.length; i++) {
const platform = { name: context.opts.platforms[i], path: context.opts.paths[i] }
platforms.push(platform)
}
return new Promise((resolve, reject) => {
async.each(platforms, function (platform, callback) {
console.log(`${twoSpaces}Minifying html/css/js files for ${platform.name} at ${path.relative(projectRoot, platform.path)}`)
processAllFilesForOnePlatform(platform, callback)
}, function (err) {
if (err) {
console.error(Error(err))
reject(Error(err))
} else {
console.log(`${twoSpaces}All files for all platforms have been minified successfully`)
resolve()
}
})
})
}
function processAllFilesForOnePlatform (platform, mainCallback) {
async.parallel([
(localCallback) => {
processJSfiles(platform, localCallback)
},
(localCallback) => {
processCSSFiles(platform, localCallback)
},
(localCallback) => {
processHTMLfiles(platform, localCallback)
}],
function (err, results) {
if (err) {
console.error(Error(('\nError minifying file.\n' + err.message)), err)
mainCallback(new Error(err))
} else {
console.log(`${twoSpaces + twoSpaces}All files minified successfully for ${platform.name}`)
mainCallback()
}
}
)
}
function processJSfiles (platform, callback) {
const wwwDistDir = platform.path
var walker = walk.walk(path.join(wwwDistDir, 'js'))
walker.on('file', function (root, fileStats, next) {
var filename = path.join(root, fileStats.name)
// gets file extension
if (getFileExtension(filename) === 'js' && !filename.includes('.min.js')) {
var code = fs.readFileSync(filename, 'utf-8')
var result = UglifyJS.minify(code)
if (result.error) {
callback(Error('Error minifying file: ' + path.relative(wwwDistDir, filename) + '.\n' + result.error))
console.error(result)
return
} else {
console.log(`${twoSpaces + twoSpaces}${platform.name}:${path.relative(wwwDistDir, filename)}`)
fs.writeFileSync(filename, result.code, 'utf8')
}
}
next()
})
walker.on('errors', function (root, nodeStatsArray, next) {
callback(Error('There was an error with' + nodeStatsArray.name))
})
walker.on('end', function () {
callback()
})
}
// minifies all css files on the client side, namely on the build/css/ directory,
// i.e., these are CSS files that will be sent from the server to the client
function processCSSFiles (platform, callback) {
const wwwDistDir = platform.path
var walker = walk.walk(path.join(wwwDistDir, 'css')) // dir to walk into
walker.on('file', function (root, fileStats, next) {
var filename = path.join(root, fileStats.name)
if (filename.includes('.css') && !filename.includes('.min.css')) {
var code = fs.readFileSync(filename, 'utf-8')
var result = uglifycss.processString(code)
if (!result) {
callback(Error('Error minifying file: ' + filename + '.\n'))
return
} else {
console.log(`${twoSpaces + twoSpaces}${platform.name}:${path.relative(wwwDistDir, filename)}`)
fs.writeFileSync(filename, result, 'utf8')
}
}
next()
})
walker.on('errors', function (root, nodeStatsArray, next) {
callback(Error('There was an error with' + nodeStatsArray.name))
})
walker.on('end', function () {
callback()
})
}
// minifies all html files
function processHTMLfiles (platform, callback) {
const wwwDistDir = platform.path
var walker = walk.walk(wwwDistDir) // dir to walk into
walker.on('file', function (root, fileStats, next) {
var filename = path.join(root, fileStats.name)
if (getFileExtension(filename) === 'html') {
var code = fs.readFileSync(filename, 'utf-8')
var result = minifyHTML(code, {
ignoreCustomFragments: [
/<%[\s\S]*?%>/, // ignore default fragments
/<\?[\s\S]*?\?>/
],
collapseWhitespace: true, // collapse white space that contributes to text nodes in a document tree
removeComments: true, // strip HTML comments
removeOptionalTags: true, // remove optional tags http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags
caseSensitive: true // treat attributes in case sensitive manner (useful for custom HTML tags)
})
if (!result) {
callback(Error('Error minifying file: ' + filename + '.\n'))
return
} else {
console.log(`${twoSpaces + twoSpaces}${platform.name}:${path.relative(wwwDistDir, filename)}`)
fs.writeFileSync(filename, result, 'utf8')
}
}
next()
})
walker.on('errors', function (root, nodeStatsArray, next) {
callback(Error('There was an error with' + nodeStatsArray.name))
})
walker.on('end', function () {
callback()
})
}
function getFileExtension (fileName) {
return fileName.split('.').pop()
}
Simply test it by doing cordova prepare
and then check the content of dist directory (for example in Android platforms/android/app/src/main/assets/www
) to be sure the files are minified.
Thank you for this gist.
When you say, create a file
hooks/convertHbsToHtml.js
did you meanhooks/minifyFiles.js
Since https://www.npmjs.com/package/uglify-es is deprecated, suggest to switch to: https://www.npmjs.com/package/uglify-js