Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Using browser-sync with Cordova
// Browser-Sync support for Cordova projects
// To use this, add the following snippet as a after_run hook using something like
// <hook type="after_prepare" src="hooks/browser-sync.js" />
// Also add ws: 'unsafe-inline' to the CSP in index.html
// The do Cordova run, and changing anything in www/ will live-reload the cordova app on emulator/device
module.exports = function(context) {
if (context.opts.options[0] === '--browser-sync-mode') {
// Prepare was called by this script, so don't run the script again
return;
}
var path = require('path');
var fs = require('fs');
var Url = require('url');
var debug = console.log.bind(console);
var npm = context.requireCordovaModule('npm');
var Q = context.requireCordovaModule('q');
var glob = context.requireCordovaModule('glob')
var et = context.requireCordovaModule('elementtree');
function parseXml(filename) {
return new et.ElementTree(et.XML(fs.readFileSync(filename, "utf-8").replace(/^\uFEFF/, "")));
}
// Installs browser-sync and other packages locally
function installDependencies() {
return Q();
debug('Starting npm');
return Q.ninvoke(npm, 'load').then(function() {
debug('Installing dependencies');
return Q.ninvoke(npm.commands, 'install', ['browser-sync']);
});
}
/**
* Cordova is usually served from index.html on the device. This function serves changes that to be served from a server instead
* @param configLocation - The place where platform specific config.xml is located, relative to platforms folder
* @param hostedPage - Location from where the www/index.html file should be served, relative to platforms folder
**/
function changeHost(hostedPage, configLocation) {
var cwd = path.join(context.opts.projectRoot, 'platforms', configLocation);
debug('Searching for config files at', cwd);
return configs = glob.sync('**/config.xml', {
cwd: cwd
}).map(function(filename) {
var filename = path.join(cwd, filename);
debug('Changing ', filename);
configXml = parseXml(filename);
var contentTag = configXml.find('content[@src]');
if (contentTag) {
contentTag.attrib.src = hostedPage;
}
// Also add allow nav in case of
var allowNavTag = et.SubElement(configXml.find('.'), 'allow-navigation');
allowNavTag.set('href', '*');
fs.writeFileSync(filename, configXml.write({
indent: 4
}), "utf-8");
return filename;
});
}
/**
* Starts the browser sync server, and when files are changed, does the reload
* @param location - where to watch
* @returns location where files are served from
*/
function browserSyncServer(location) {
debug('Starting browser-sync server');
location = location + '**/*.*'
return Q.promise(function(resolve, reject, notify) {
var bs = require('browser-sync').create();
bs.watch(location, function(event, files) {
if (event !== 'change') {
return;
}
// TODO Prepare only the platform that was run
context.cordova.prepare({
options: ['--browser-sync-mode'] // to indicate who is calling prepare
});
bs.reload(files);
});
bs.init({
server: {
baseDir: context.opts.projectRoot,
directory: true
},
files: location,
open: false,
snippetOptions: {
rule: {
match: /<\/body>/i,
fn: function(snippet, match) {
return '<script>window.__karma__=true</script>' + snippet + monkeyPatch() + match;
}
}
},
minify: false
}, function(err, bs) {
var server = bs.options.getIn(['urls', 'external']);
resolve(Url.resolve(server, 'platforms'));
});
});
}
// Main workfow
return installDependencies().then(function() {
return browserSyncServer(path.join(context.opts.projectRoot, 'www/'));
}).then(function(server) {
// TODO - Change host based on platform
changeHost(server + '/android/assets/www/index.html', 'android/res');
changeHost(server + '/ios/www/index.html', 'ios');
});
};
function monkeyPatch() {
var script = function() {
(function patch() {
if (typeof window.__bs === 'undefined') {
window.setTimeout(patch, 500);
} else {
var oldCanSync = window.__bs.prototype.canSync;
window.__bs.prototype.canSync = function(data, optPath) {
data.url = window.location.pathname.substr(0, window.location.pathname.indexOf('/www')) + data.url.substr(data.url.indexOf('/www'))
return oldCanSync.apply(this, [data, optPath]);
};
}
}());
};
return '<script>(' + script.toString() + '());</script>';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment