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
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("browser-sync-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
|