You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

565 lines
14 KiB

"use strict";
var hooks = require("./hooks");
var asyncTasks = require("./async-tasks");
var config = require("./config");
var connectUtils = require("./connect-utils");
var utils = require("./utils");
var logger = require("./logger");
var eachSeries = utils.eachSeries;
var _ = require("./lodash.custom");
var EE = require("easy-extender");
/**
* Required internal plugins.
* Any of these can be overridden by deliberately
* causing a name-clash.
*/
var defaultPlugins = {
logger: logger,
socket: require("./sockets"),
"file:watcher": require("./file-watcher"),
server: require("./server"),
tunnel: require("./tunnel"),
"client:script": require("./client"),
UI: require("browser-sync-ui")
};
/**
* @constructor
*/
var BrowserSync = function (emitter) {
var bs = this;
bs.cwd = process.cwd();
bs.active = false;
bs.paused = false;
bs.config = config;
bs.utils = utils;
bs.events = bs.emitter = emitter;
bs._userPlugins = [];
bs._reloadQueue = [];
bs._cleanupTasks = [];
bs._browserReload = false;
// Plugin management
bs.pluginManager = new EE(defaultPlugins, hooks);
};
/**
* Call a user-options provided callback
* @param name
*/
BrowserSync.prototype.callback = function (name) {
var bs = this;
var cb = bs.options.getIn(["callbacks", name]);
if (_.isFunction(cb)) {
cb.apply(bs.publicInstance, _.toArray(arguments).slice(1));
}
};
/**
* @param {Map} options
* @param {Function} cb
* @returns {BrowserSync}
*/
BrowserSync.prototype.init = function (options, cb) {
/**
* Safer access to `this`
* @type {BrowserSync}
*/
var bs = this;
/**
* Set user-provided callback, or assign a noop
* @type {Function}
*/
bs.cb = cb || utils.defaultCallback;
/**
* Verify provided config.
* Some options are not compatible and will cause us to
* end the process.
*/
if (!utils.verifyConfig(options, bs.cb)) {
return;
}
/**
* Save a reference to the original options
* @type {Map}
* @private
*/
bs._options = options;
/**
* Set additional options that depend on what the
* user may of provided
* @type {Map}
*/
bs.options = options;
/**
* Kick off default plugins.
*/
bs.pluginManager.init();
/**
* Create a base logger & debugger.
*/
bs.logger = bs.pluginManager.get("logger")(bs.events, bs);
bs.debugger = bs.logger.clone({ useLevelPrefixes: true });
bs.debug = bs.debugger.debug;
/**
* Run each setup task in sequence
*/
eachSeries(asyncTasks, taskRunner(bs), tasksComplete(bs));
return this;
};
/**
* Run 1 setup task.
* Each task is a pure function.
* They can return options or instance properties to set,
* but they cannot set them directly.
* @param {BrowserSync} bs
* @returns {Function}
*/
function taskRunner(bs) {
return function (item, cb) {
bs.debug("-> {yellow:Starting Step: " + item.step);
/**
* Execute the current task.
*/
item.fn(bs, executeTask);
function executeTask(err, out) {
/**
* Exit early if any task returned an error.
*/
if (err) {
return cb(err);
}
/**
* Act on return values (such as options to be set,
* or instance properties to be set
*/
if (out) {
handleOut(bs, out);
}
bs.debug("+ {green:Step Complete: " + item.step);
cb();
}
};
}
/**
* @param bs
* @param out
*/
function handleOut(bs, out) {
/**
* Set a single/many option.
*/
if (out.options) {
setOptions(bs, out.options);
}
/**
* Any options returned that require path access?
*/
if (out.optionsIn) {
out.optionsIn.forEach(function (item) {
bs.setOptionIn(item.path, item.value);
});
}
/**
* Any instance properties returned?
*/
if (out.instance) {
Object.keys(out.instance).forEach(function (key) {
bs[key] = out.instance[key];
});
}
}
/**
* Update the options Map
* @param bs
* @param options
*/
function setOptions(bs, options) {
/**
* If multiple options were set, act on the immutable map
* in an efficient way
*/
if (Object.keys(options).length > 1) {
bs.setMany(function (item) {
Object.keys(options).forEach(function (key) {
item.set(key, options[key]);
return item;
});
});
}
else {
Object.keys(options).forEach(function (key) {
bs.setOption(key, options[key]);
});
}
}
/**
* At this point, ALL async tasks have completed
* @param {BrowserSync} bs
* @returns {Function}
*/
function tasksComplete(bs) {
return function (err) {
if (err) {
bs.logger.setOnce("useLevelPrefixes", true).error(err.message);
}
/**
* Set active flag
*/
bs.active = true;
/**
* @deprecated
*/
bs.events.emit("init", bs);
/**
* This is no-longer needed as the Callback now only resolves
* when everything (including slow things, like the tunnel) is ready.
* It's here purely for backwards compatibility.
* @deprecated
*/
bs.events.emit("service:running", {
options: bs.options,
baseDir: bs.options.getIn(["server", "baseDir"]),
type: bs.options.get("mode"),
port: bs.options.get("port"),
url: bs.options.getIn(["urls", "local"]),
urls: bs.options.get("urls").toJS(),
tunnel: bs.options.getIn(["urls", "tunnel"])
});
/**
* Call any option-provided callbacks
*/
bs.callback("ready", null, bs);
/**
* Finally, call the user-provided callback given as last arg
*/
bs.cb(null, bs);
};
}
/**
* @param module
* @param opts
* @param cb
*/
BrowserSync.prototype.registerPlugin = function (module, opts, cb) {
var bs = this;
bs.pluginManager.registerPlugin(module, opts, cb);
if (module["plugin:name"]) {
bs._userPlugins.push(module);
}
};
/**
* Get a plugin by name
* @param name
*/
BrowserSync.prototype.getUserPlugin = function (name) {
var bs = this;
var items = bs.getUserPlugins(function (item) {
return item["plugin:name"] === name;
});
if (items && items.length) {
return items[0];
}
return false;
};
/**
* @param {Function} [filter]
*/
BrowserSync.prototype.getUserPlugins = function (filter) {
var bs = this;
filter =
filter ||
function () {
return true;
};
/**
* Transform Plugins option
*/
bs.userPlugins = bs._userPlugins.filter(filter).map(function (plugin) {
return {
name: plugin["plugin:name"],
active: plugin._enabled,
opts: bs.pluginManager.pluginOptions[plugin["plugin:name"]]
};
});
return bs.userPlugins;
};
/**
* Get middleware
* @returns {*}
*/
BrowserSync.prototype.getMiddleware = function (type) {
var types = {
connector: connectUtils.socketConnector(this.options)
};
if (type in types) {
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(types[type]);
};
}
};
/**
* Shortcut for pushing a file-serving middleware
* onto the stack
* @param {String} path
* @param {{type: string, content: string}} props
*/
var _serveFileCount = 0;
BrowserSync.prototype.serveFile = function (path, props) {
var bs = this;
var mode = bs.options.get("mode");
var entry = {
handle: function (req, res) {
res.setHeader("Content-Type", props.type);
res.end(props.content);
},
id: "Browsersync - " + _serveFileCount++,
route: path
};
bs._addMiddlewareToStack(entry);
};
/**
* Add middlewares on the fly
*/
BrowserSync.prototype._addMiddlewareToStack = function (entry) {
var bs = this;
/**
* additional middlewares are always appended -1,
* this is to allow the proxy middlewares to remain,
* and the directory index to remain in serveStatic/snippet modes
*/
bs.app.stack.splice(bs.app.stack.length - 1, 0, entry);
};
var _addMiddlewareCount = 0;
BrowserSync.prototype.addMiddleware = function (route, handle, opts) {
var bs = this;
if (!bs.app) {
return;
}
opts = opts || {};
if (!opts.id) {
opts.id = "bs-mw-" + _addMiddlewareCount++;
}
if (route === "*") {
route = "";
}
var entry = {
id: opts.id,
route: route,
handle: handle
};
if (opts.override) {
entry.override = true;
}
bs.options = bs.options.update("middleware", function (mw) {
if (bs.options.get("mode") === "proxy") {
return mw.insert(mw.size - 1, entry);
}
return mw.concat(entry);
});
bs.resetMiddlewareStack();
};
/**
* Remove middlewares on the fly
* @param {String} id
* @returns {Server}
*/
BrowserSync.prototype.removeMiddleware = function (id) {
var bs = this;
if (!bs.app) {
return;
}
bs.options = bs.options.update("middleware", function (mw) {
return mw.filter(function (mw) {
return mw.id !== id;
});
});
bs.resetMiddlewareStack();
};
/**
* Middleware for socket connection (external usage)
* @param opts
* @returns {*}
*/
BrowserSync.prototype.getSocketConnector = function (opts) {
var bs = this;
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(bs.getExternalSocketConnector(opts));
};
};
/**
* Socket connector as a string
* @param {Object} opts
* @returns {*}
*/
BrowserSync.prototype.getExternalSocketConnector = function (opts) {
var bs = this;
return connectUtils.socketConnector(bs.options.withMutations(function (item) {
item.set("socket", item.get("socket").merge(opts));
if (!bs.options.getIn(["proxy", "ws"])) {
item.set("mode", "snippet");
}
}));
};
/**
* Callback helper
* @param name
*/
BrowserSync.prototype.getOption = function (name) {
this.debug("Getting option: {magenta:%s", name);
return this.options.get(name);
};
/**
* Callback helper
* @param path
*/
BrowserSync.prototype.getOptionIn = function (path) {
this.debug("Getting option via path: {magenta:%s", path);
return this.options.getIn(path);
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getOptions = function () {
return this.options;
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getLogger = logger.getLogger;
/**
* @param {String} name
* @param {*} value
* @returns {BrowserSync.options|*}
*/
BrowserSync.prototype.setOption = function (name, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", name, value.toString());
bs.options = bs.options.set(name, value);
if (!opts.silent) {
bs.events.emit("options:set", {
path: name,
value: value,
options: bs.options
});
}
return this.options;
};
/**
* @param path
* @param value
* @param opts
* @returns {Map|*|BrowserSync.options}
*/
BrowserSync.prototype.setOptionIn = function (path, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", path.join("."), value.toString());
bs.options = bs.options.setIn(path, value);
if (!opts.silent) {
bs.events.emit("options:set", {
path: path,
value: value,
options: bs.options
});
}
return bs.options;
};
/**
* Set multiple options with mutations
* @param fn
* @param opts
* @returns {Map|*}
*/
BrowserSync.prototype.setMany = function (fn, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting multiple Options");
bs.options = bs.options.withMutations(fn);
if (!opts.silent) {
bs.events.emit("options:set", { options: bs.options.toJS() });
}
return this.options;
};
BrowserSync.prototype.addRewriteRule = function (rule) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.concat(rule);
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.removeRewriteRule = function (id) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.filter(function (rule) {
return rule.id !== id;
});
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.setRewriteRules = function (rules) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (_) {
return rules;
});
bs.resetMiddlewareStack();
};
/**
* Add a new rewrite rule to the stack
* @param {Object} rule
*/
BrowserSync.prototype.resetMiddlewareStack = function () {
var bs = this;
var middlewares = require("./server/utils").getMiddlewares(bs, bs.options);
bs.app.stack = middlewares;
};
/**
* @param fn
*/
BrowserSync.prototype.registerCleanupTask = function (fn) {
this._cleanupTasks.push(fn);
};
/**
* Instance Cleanup
*/
BrowserSync.prototype.cleanup = function (cb) {
var bs = this;
if (!bs.active) {
return;
}
// Remove all event listeners
if (bs.events) {
bs.debug("Removing event listeners...");
bs.events.removeAllListeners();
}
// Close any core file watchers
if (bs.watchers) {
Object.keys(bs.watchers).forEach(function (key) {
bs.watchers[key].watchers.forEach(function (watcher) {
watcher.close();
});
});
}
// Run any additional clean up tasks
bs._cleanupTasks.forEach(function (fn) {
if (_.isFunction(fn)) {
fn(bs);
}
});
// Reset the flag
bs.debug("Setting {magenta:active: false");
bs.active = false;
bs.paused = false;
bs.pluginManager.plugins = {};
bs.pluginManager.pluginOptions = {};
bs.pluginManager.defaultPlugins = defaultPlugins;
bs._userPlugins = [];
bs.userPlugins = [];
bs._reloadTimer = undefined;
bs._reloadQueue = [];
bs._cleanupTasks = [];
if (_.isFunction(cb)) {
cb(null, bs);
}
};
module.exports = BrowserSync;
//# sourceMappingURL=browser-sync.js.map