blob: 3798e2fa9bd1288cd1de4ec5e871fbbd3b6cbdd0 [file] [log] [blame]
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var path = require('path'),
fs = require('fs'),
shell = require('shelljs'),
cordova_util = require('./util'),
util = require('util'),
android_parser = require('./metadata/android_parser'),
blackberry_parser= require('./metadata/blackberry_parser'),
ios_parser = require('./metadata/ios_parser'),
exec = require('child_process').exec,
et = require('elementtree');
// Called during cordova prepare.
// Sets up each plugin's Javascript code to be loaded properly.
module.exports = function plugin_loader(platform) {
// Process:
// - List all plugins in plugins/.
// - Load and parse their plugin.xml files.
// - Skip those without support for this platform.
// - Build a list of all their js-modules, and platform-specific js-modules.
// - For each js-module (general first, then platform):
// - Generate JS code to load it.
// - For each <clobbers>, <merges> or <runs>, generate JS code to perform it.
// - Copy the file, having slapped the cordova.define onto it on the way.
// - Append all of this code to the platform's cordova.js
var projectRoot = cordova_util.isCordova(process.cwd());
var plugins_dir = path.join(projectRoot, 'plugins');
var plugins = cordova_util.findPlugins(plugins_dir);
// Placed at the top of cordova.js to delay onDeviceReady until all the plugins
// are actually loaded. This is a temporary hack that can be removed once this
// prototype is rolled into the main code.
var topJS = 'window.__onPluginsLoadedHack = true;\n';
// The main injected JS, used to inject <script> tags.
var js = '';
// The last part of the injected JS, which runs after all plugins are loaded and
// registers the clobbers, merges and runs.
var lateJS = '';
// Add the callback function.
js += 'var mapper = cordova.require("cordova/modulemapper");\n';
js += 'var scriptCounter = 0;\n';
js += 'var scriptCallback = function() {\n';
js += 'scriptCounter--;\n';
js += 'if (scriptCounter == 0) { scriptsLoaded(); } };\n';
// <script> tag injection function
js += 'function injectScript(path) {\n';
js += 'scriptCounter++;\n';
js += 'var script = document.createElement("script");\n';
js += 'script.onload = scriptCallback;\n';
js += 'script.src = path;\n';
js += 'document.querySelector("head").appendChild(script);\n';
js += '}\n\n';
// Acquire the platform's parser.
var parser;
switch(platform) {
case 'android':
parser = new android_parser(path.join(projectRoot, 'platforms', 'android'));
break;
case 'ios':
parser = new ios_parser(path.join(projectRoot, 'platforms', 'ios'));
break;
case 'blackberry':
parser = new blackberry_parser(path.join(projectRoot, 'platforms', 'blackberry'));
break;
}
plugins && plugins.forEach(function(plugin) {
var pluginDir = path.join(projectRoot, 'plugins', plugin);
var xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(pluginDir, 'plugin.xml'), 'utf-8')));
var plugin_id = xml.getroot().attrib.id;
// Copy all the <asset>s into the platform's www/
var assets = xml.findall('./asset');
assets && assets.forEach(function(asset) {
var target = asset.attrib.target;
var lastSlash = target.lastIndexOf('/');
var dirname = lastSlash < 0 ? '' : target.substring(0, lastSlash);
var basename = lastSlash < 0 ? target : target.substring(lastSlash + 1);
var targetDir = path.join(parser.www_dir(), dirname);
shell.mkdir('-p', targetDir);
var srcFile = path.join(pluginDir, asset.attrib.src);
var targetFile = path.join(targetDir, basename);
var cpOptions = fs.statSync(srcFile).isDirectory() ? '-Rf' : '-f';
shell.cp(cpOptions, [srcFile], targetFile);
});
// And then add the plugins dir to the platform's www.
var platformPluginsDir = path.join(parser.www_dir(), 'plugins');
shell.mkdir('-p', platformPluginsDir);
var generalModules = xml.findall('./js-module');
var platformTag = xml.find(util.format('./platform[@name="%s"]', platform));
if (!platformTag) {
return; // Skip plugins that don't support this platform.
}
var platformModules = platformTag.findall('./js-module');
generalModules = generalModules || [];
var allModules = generalModules.concat(platformModules);
allModules.forEach(function(module) {
// Copy the plugin's files into the www directory.
var dirname = module.attrib.src;
var lastSlash = dirname.lastIndexOf('/');
if (lastSlash >= 0) {
dirname = dirname.substring(0, lastSlash);
} else {
dirname = ''; // Just the file, no subdir.
}
var dir = path.join(platformPluginsDir, plugin_id, dirname);
shell.mkdir('-p', dir);
// Read in the file, prepend the cordova.define, and write it back out.
var moduleName = plugin_id + '.';
if (module.attrib.name) {
moduleName += module.attrib.name;
} else {
var result = module.attrib.src.match(/([^\/]+)\.js/);
moduleName += result[1];
}
var scriptContent = fs.readFileSync(path.join(pluginDir, module.attrib.src), 'utf-8');
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {' + scriptContent + '});\n';
fs.writeFileSync(path.join(platformPluginsDir, plugin_id, module.attrib.src), scriptContent, 'utf-8');
// Prepare the injected Javascript code.
var jsFile = path.join('plugins', plugin_id, module.attrib.src);
js += 'injectScript("' + jsFile + '");\n';
// Loop over the children, injecting clobber, merge and run code for each.
module.getchildren().forEach(function(child) {
if (child.tag.toLowerCase() == 'clobbers') {
lateJS += 'mapper.clobbers("' + moduleName + '", "' + child.attrib.target + '");\n';
} else if (child.tag.toLowerCase() == 'merges') {
lateJS += 'mapper.merges("' + moduleName + '", "' + child.attrib.target + '");\n';
} else if (child.tag.toLowerCase() == 'runs') {
lateJS += 'cordova.require("' + moduleName + '");\n';
}
});
lateJS += '\n\n\n';
});
});
// Last step in lateJS that runs after the <script> tags have all loaded and
// all modules are properly clobbered: fire onPluginsReady event.
lateJS += 'cordova.require("cordova/channel").onPluginsReady.fire();\n';
// Wrap lateJS into scriptsLoaded(), which will be called after the last <script>
// has finished loading.
lateJS = 'function scriptsLoaded() {\n' + lateJS + '\n}\n';
// Now write the generated JS to the platform's cordova.js
var cordovaJSPath = path.join(parser.www_dir(), 'cordova.js');
var cordovaJS = fs.readFileSync(cordovaJSPath, 'utf-8');
cordovaJS = topJS + cordovaJS + '(function() { ' + js + lateJS + '})();';
fs.writeFileSync(cordovaJSPath, cordovaJS, 'utf-8');
};