index.js 5.54 KB
/* global __resourceQuery */
var url = require("url");
var stripAnsi = require("strip-ansi");
var socket = require("./socket");
var overlay = require("./overlay");

function getCurrentScriptSource() {
	// `document.currentScript` is the most accurate way to find the current script,
	// but is not supported in all browsers.
	if(document.currentScript)
		return document.currentScript.getAttribute("src");
	// Fall back to getting all scripts in the document.
	var scriptElements = document.scripts || [];
	var currentScript = scriptElements[scriptElements.length - 1];
	if(currentScript)
		return currentScript.getAttribute("src");
	// Fail as there was no script to use.
	throw new Error("[WDS] Failed to get current script source");
}

var urlParts;
if(typeof __resourceQuery === "string" && __resourceQuery) {
	// If this bundle is inlined, use the resource query to get the correct url.
	urlParts = url.parse(__resourceQuery.substr(1));
} else {
	// Else, get the url from the <script> this file was called with.
	var scriptHost = getCurrentScriptSource();
	scriptHost = scriptHost.replace(/\/[^\/]+$/, "");
	urlParts = url.parse((scriptHost ? scriptHost : "/"), false, true);
}

var hot = false;
var initial = true;
var currentHash = "";
var logLevel = "info";
var useWarningOverlay = false;
var useErrorOverlay = false;

function log(level, msg) {
	if(logLevel === "info" && level === "info")
		return console.log(msg);
	if(["info", "warning"].indexOf(logLevel) >= 0 && level === "warning")
		return console.warn(msg);
	if(["info", "warning", "error"].indexOf(logLevel) >= 0 && level === "error")
		return console.error(msg);
}

// Send messages to the outside, so plugins can consume it.
function sendMsg(type, data) {
	if(typeof self !== "undefined" && self.window) {
		self.postMessage({
			type: "webpack" + type,
			data: data
		}, "*");
	}
}

var onSocketMsg = {
	hot: function() {
		hot = true;
		log("info", "[WDS] Hot Module Replacement enabled.");
	},
	invalid: function() {
		log("info", "[WDS] App updated. Recompiling...");
		sendMsg("Invalid");
	},
	hash: function(hash) {
		currentHash = hash;
	},
	"still-ok": function() {
		log("info", "[WDS] Nothing changed.")
		if(useWarningOverlay || useErrorOverlay) overlay.clear();
		sendMsg("StillOk");
	},
	"log-level": function(level) {
		logLevel = level;
	},
	"overlay": function(overlay) {
		if(typeof document !== "undefined") {
			if(typeof(overlay) === "boolean") {
				useWarningOverlay = overlay;
				useErrorOverlay = overlay;
			} else if(overlay) {
				useWarningOverlay = overlay.warnings;
				useErrorOverlay = overlay.errors;
			}
		}
	},
	ok: function() {
		sendMsg("Ok");
		if(useWarningOverlay || useErrorOverlay) overlay.clear();
		if(initial) return initial = false;
		reloadApp();
	},
	"content-changed": function() {
		log("info", "[WDS] Content base changed. Reloading...")
		self.location.reload();
	},
	warnings: function(warnings) {
		log("info", "[WDS] Warnings while compiling.");
		var strippedWarnings = warnings.map(function(warning) {
			return stripAnsi(warning);
		});
		sendMsg("Warnings", strippedWarnings);
		for(var i = 0; i < strippedWarnings.length; i++)
			console.warn(strippedWarnings[i]);
		if(useWarningOverlay) overlay.showMessage(warnings);

		if(initial) return initial = false;
		reloadApp();
	},
	errors: function(errors) {
		log("info", "[WDS] Errors while compiling. Reload prevented.");
		var strippedErrors = errors.map(function(error) {
			return stripAnsi(error);
		});
		sendMsg("Errors", strippedErrors);
		for(var i = 0; i < strippedErrors.length; i++)
			console.error(strippedErrors[i]);
		if(useErrorOverlay) overlay.showMessage(errors);
	},
	error: function(error) {
		console.error(error);
	},
	close: function() {
		log("error", "[WDS] Disconnected!");
		sendMsg("Close");
	}
};

var hostname = urlParts.hostname;
var protocol = urlParts.protocol;


//check ipv4 and ipv6 `all hostname`
if(hostname === "0.0.0.0" || hostname === "::") {
	// why do we need this check?
	// hostname n/a for file protocol (example, when using electron, ionic)
	// see: https://github.com/webpack/webpack-dev-server/pull/384
	if(self.location.hostname && !!~self.location.protocol.indexOf("http")) {
		hostname = self.location.hostname;
	}
}

// `hostname` can be empty when the script path is relative. In that case, specifying
// a protocol would result in an invalid URL.
// When https is used in the app, secure websockets are always necessary
// because the browser doesn't accept non-secure websockets.
if(hostname && (self.location.protocol === "https:" || urlParts.hostname === "0.0.0.0")) {
	protocol = self.location.protocol;
}

var socketUrl = url.format({
	protocol: protocol,
	auth: urlParts.auth,
	hostname: hostname,
	port: (urlParts.port === "0") ? self.location.port : urlParts.port,
	pathname: urlParts.path == null || urlParts.path === "/" ? "/sockjs-node" : urlParts.path
});

socket(socketUrl, onSocketMsg);

var isUnloading = false;
self.addEventListener("beforeunload", function() {
	isUnloading = true;
});

function reloadApp() {
	if(isUnloading) {
		return;
	}
	if(hot) {
		log("info", "[WDS] App hot update...");
		var hotEmitter = require("webpack/hot/emitter");
		hotEmitter.emit("webpackHotUpdate", currentHash);
		if(typeof self !== "undefined" && self.window) {
			// broadcast update to window
			self.postMessage("webpackHotUpdate" + currentHash, "*");
		}
	} else {
		log("info", "[WDS] App updated. Reloading...");
		self.location.reload();
	}
}