vite-plugin-browser-sync
Version:
Add BrowserSync in your Vite project
307 lines (304 loc) • 8.22 kB
JavaScript
import { bold, lightYellow, red } from "kolorist";
import process from "node:process";
import { create } from "browser-sync";
//#region src/server.ts
const defaultPorts = {
dev: 5173,
preview: 4173,
buildWatch: null
};
/**
* Hook browsersync server on vite
*/
var Server = class {
constructor(obj) {
this.logged = false;
const { name, server, config, options, env } = obj;
this.name = name;
this.server = server;
this.config = config;
this.options = options;
this.env = env;
this.bsServer = create(this.name);
if (typeof this.userBsOptions.logLevel === "undefined") this.logged = true;
this.registerInit();
this.registerClose();
}
/**
* Get browser sync mode
* @readonly
*/
get mode() {
if (this.env === "preview") return "proxy";
let mode = this.userOptions && "mode" in this.userOptions && this.userOptions.mode ? this.userOptions.mode : "proxy";
if (this.userBsOptions.proxy) mode = "proxy";
return mode;
}
/**
* Get browser sync instance
* @readonly
*/
get bs() {
return this.bsServer;
}
/**
* Get vite server port
* @readonly
*/
get port() {
if (this.env === "buildWatch" || !this.server) return null;
const defaultPort = defaultPorts[this.env];
return (this.env === "dev" ? this.config.server.port : this.config.preview.port) || defaultPort;
}
/**
* Get user options
* @readonly
*/
get userOptions() {
return this.options && this.env in this.options ? this.options[this.env] : {};
}
/**
* Get user browsersync options
* @readonly
*/
get userBsOptions() {
return this.userOptions && this.userOptions.bs ? this.userOptions.bs : {};
}
/**
* Get Final browsersync options
*/
get bsOptions() {
const bsOptions = this.userBsOptions;
if (typeof bsOptions.logLevel === "undefined") bsOptions.logLevel = "silent";
if (typeof bsOptions.open !== "undefined") {
if (this.env === "dev" && typeof this.config.server.open === "boolean") bsOptions.open = false;
else if (this.env === "preview" && typeof this.config.preview.open === "boolean") bsOptions.open = false;
}
if (this.env === "dev" && typeof bsOptions.codeSync === "undefined") bsOptions.codeSync = false;
if (this.mode === "snippet") {
bsOptions.logSnippet = false;
bsOptions.snippet = false;
}
bsOptions.online = bsOptions.online === true || this.server && typeof this.config.server.host !== "undefined" || false;
if (this.env === "buildWatch") return bsOptions;
if (this.mode === "proxy") {
let target;
if (this.server?.resolvedUrls?.local[0]) target = this.server?.resolvedUrls?.local[0];
else if (this.port) target = `${this.config.server.https ? "https" : "http"}://localhost:${this.port}/`;
if (!bsOptions.proxy) bsOptions.proxy = {
target,
ws: true
};
else if (typeof bsOptions.proxy === "string") bsOptions.proxy = {
target: bsOptions.proxy,
ws: true
};
else if (typeof bsOptions.proxy === "object" && !bsOptions.proxy.ws) bsOptions.proxy.ws = true;
}
return bsOptions;
}
/**
* Init browsersync server
*/
init() {
return new Promise((resolve, reject) => {
this.bsServer.init(this.bsOptions, (error, bs) => {
if (error) {
this.config.logger.error(red(`[vite-plugin-browser-sync] ${error.name} ${error.message}`), { error });
reject(error);
}
resolve(bs);
});
});
}
/* c8 ignore start */
/**
* Log browsersync infos
*/
log() {
const colorUrl = (url) => lightYellow(url.replace(/:(\d+)$/, (_, port) => `:${bold(port)}/`));
const urls = this.bsServer.getOption("urls").toJS();
const consoleTexts = {
"local": "Local",
"external": "External",
"ui": "UI",
"ui-external": "UI External",
"tunnel": "Tunnel"
};
for (const key in consoleTexts) if (Object.prototype.hasOwnProperty.call(consoleTexts, key)) {
const text = consoleTexts[key];
if (Object.prototype.hasOwnProperty.call(urls, key)) this.config.logger.info(` ${lightYellow("➜")} ${bold(`BrowserSync - ${text}`)}: ${colorUrl(urls[key])}`);
}
}
/**
* Register log function on vite
*/
registerLog() {
if (!this.logged) return;
if (this.server && this.env === "dev") {
let astroServer = false;
try {
astroServer = "pluginContainer" in this.server && this.server.environments.client.plugins.findIndex((plugin) => plugin.name === "astro:server") > -1;
} catch {}
if (astroServer) setTimeout(() => this.log(), 1e3);
else {
const _print = this.server.printUrls;
this.server.printUrls = () => {
_print();
this.log();
};
}
} else this.log();
}
/* c8 ignore stop */
/**
* Register init
*/
async registerInit() {
if (this.server && "listen" in this.server) {
const _listen = this.server.listen;
this.server.listen = async () => {
const out = await _listen();
await this.init();
return out;
};
} else if (this.server) {
await new Promise((resolve) => {
this.server?.httpServer?.once("listening", () => {
resolve(true);
});
});
await this.init();
} else await this.init();
this.registerLog();
}
/**
* Register close
*/
registerClose() {
if (this.server) {
const _close = this.server.close;
this.server.close = async () => {
this.bsServer.exit();
await _close();
};
this.server.httpServer?.on("close", () => {
this.bsServer.exit();
});
}
process.once("SIGINT", () => {
this.bsServer.exit();
process.exit();
});
}
};
//#endregion
//#region src/index.ts
/**
* Vite plugin
*
* @example <caption>Basic Usage</caption>
* ```ts
* // vite.config.js / vite.config.ts
* import VitePluginBrowserSync from 'vite-plugin-browser-sync'
*
* export default {
* plugins: [VitePluginBrowserSync()]
* }
* ```
* @example <caption>With options</caption>
* ```ts
* // vite.config.js / vite.config.ts
* import VitePluginBrowserSync from 'vite-plugin-browser-sync'
*
* export default {
* plugins: [
* VitePluginBrowserSync({
* dev: {
* bs: {
* ui: {
* port: 8080
* },
* notify: false
* }
* }
* })
* ]
* }
* ```
*/
function VitePluginBrowserSync(options) {
const name = "vite-plugin-browser-sync";
const bsClientVersion = "3.0.4";
let config;
let env = "dev";
let bsServer = null;
let started = false;
let applyOnDev = false;
let applyOnPreview = false;
let applyOnBuildWatch = false;
return {
name,
apply(_config, env$1) {
applyOnDev = env$1.command === "serve" && (typeof env$1.isPreview === "undefined" || env$1.isPreview === false) && options?.dev?.enable !== false;
applyOnPreview = env$1.command === "serve" && env$1.isPreview === true && options?.preview?.enable === true;
applyOnBuildWatch = env$1.command === "build" && (_config.build?.watch === true || typeof _config.build?.watch === "object") && options?.buildWatch?.enable === true;
if (applyOnBuildWatch && options?.buildWatch?.mode !== "snippet" && typeof options?.buildWatch?.bs?.proxy !== "string" && typeof options?.buildWatch?.bs?.proxy?.target !== "string") {
console.error(red("[vite-plugin-browser-sync] You need to set a browsersync target."));
return false;
}
return applyOnDev || applyOnPreview || applyOnBuildWatch;
},
configResolved(_config) {
config = _config;
},
buildStart() {
if (started || !applyOnBuildWatch) return;
env = "buildWatch";
bsServer = new Server({
env,
name,
options,
config
});
started = true;
},
async configureServer(server) {
env = "dev";
bsServer = new Server({
env,
name,
server,
options,
config
});
},
async configurePreviewServer(server) {
env = "preview";
bsServer = new Server({
env,
name,
server,
options,
config
});
},
transformIndexHtml: {
order: "post",
handler: (html) => {
const applySnippet = applyOnDev || applyOnBuildWatch;
if (!bsServer || bsServer.mode !== "snippet" || !applySnippet) return html;
return [{
tag: "script",
attrs: {
async: "",
src: `${bsServer.bs.getOption("urls").toJS().local}/browser-sync/browser-sync-client.js?v=${bsClientVersion}`
},
injectTo: "body"
}];
}
}
};
}
//#endregion
export { VitePluginBrowserSync as default };