• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Applelo / vite-plugin-browser-sync / 24302871013

12 Apr 2026 08:52AM UTC coverage: 93.805% (+1.1%) from 92.748%
24302871013

push

github

Applelo
fix: update readme screenshot

99 of 108 branches covered (91.67%)

Branch coverage included in aggregate %.

113 of 118 relevant lines covered (95.76%)

15.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.41
/src/server.ts
1
import type { BrowserSyncInstance, Options as BrowserSyncOptions } from 'browser-sync'
2
import type { ResolvedConfig } from 'vite'
3
import type { BsMode, Env, Options, OptionsBuildWatch, OptionsDev, OptionsPreview, ViteServer } from './types'
4
import process from 'node:process'
5
import { create } from 'browser-sync'
6
import { bold, lightYellow, red } from 'kolorist'
7

8
const defaultPorts: Record<Env, number | null> = {
4✔
9
  dev: 5173,
10
  preview: 4173,
11
  buildWatch: null,
12
}
13

14
const TRAILING_PORT_REGEX = /:(\d+)$/
4✔
15

16
/**
17
 * Hook browsersync server on vite
18
 */
19
export class Server {
20
  private name: string
21
  private server?: ViteServer
22
  private options?: Options
23
  private config: ResolvedConfig
24
  private env: Env
25
  private bsServer: BrowserSyncInstance
26
  private logged: boolean = false
19✔
27

28
  constructor(obj: {
29
    name: string
30
    server?: ViteServer
31
    options?: Options
32
    config: ResolvedConfig
33
    env: Env
34
  }) {
35
    const { name, server, config, options, env } = obj
19✔
36
    this.name = name
19✔
37
    this.server = server
19✔
38
    this.config = config
19✔
39
    this.options = options
19✔
40
    this.env = env
19✔
41

42
    this.bsServer = create(this.name)
19✔
43

44
    if (typeof this.userBsOptions.logLevel === 'undefined')
19✔
45
      this.logged = true
14✔
46

47
    this.registerInit()
19✔
48
    this.registerClose()
19✔
49
  }
50

51
  /**
52
   * Get browser sync mode
53
   * @readonly
54
   */
55
  public get mode(): BsMode {
56
    if (this.env === 'preview')
44✔
57
      return 'proxy'
10✔
58
    let mode: BsMode = this.userOptions
34✔
59
      && 'mode' in this.userOptions
60
      && this.userOptions.mode
61
      ? this.userOptions.mode
62
      : 'proxy'
63

64
    if (this.userBsOptions.proxy)
44✔
65
      mode = 'proxy'
25✔
66

67
    return mode
34✔
68
  }
69

70
  /**
71
   * Get browser sync instance
72
   * @readonly
73
   */
74
  public get bs(): BrowserSyncInstance {
75
    return this.bsServer
2✔
76
  }
77

78
  /**
79
   * Get vite server port
80
   * @readonly
81
   */
82
  private get port(): number | null {
83
    if (this.env === 'buildWatch' || !this.server)
10!
84
      return null
×
85
    const defaultPort = defaultPorts[this.env]
10✔
86
    const configPort = this.env === 'dev'
10!
87
      ? this.config.server.port
88
      : this.config.preview.port
89
    return configPort || defaultPort
10!
90
  }
91

92
  /**
93
   * Get user options
94
   *  @readonly
95
   */
96
  private get userOptions(): OptionsPreview | OptionsBuildWatch | OptionsDev | undefined {
97
    return this.options && this.env in this.options
294!
98
      ? this.options[this.env]
99
      : {}
100
  }
101

102
  /**
103
   * Get user browsersync options
104
   *  @readonly
105
   */
106
  private get userBsOptions(): BrowserSyncOptions {
107
    return this.userOptions && this.userOptions.bs ? this.userOptions.bs : {}
72!
108
  }
109

110
  /**
111
   * Get Final browsersync options
112
   */
113
  private get bsOptions(): BrowserSyncOptions {
114
    const bsOptions = this.userBsOptions
19✔
115

116
    if (typeof bsOptions.logLevel === 'undefined')
19✔
117
      bsOptions.logLevel = 'silent'
14✔
118

119
    if (typeof bsOptions.open !== 'undefined') {
19!
120
      if (this.env === 'dev' && typeof this.config.server.open === 'boolean') {
19✔
121
        bsOptions.open = false
11✔
122
      }
8✔
123
      else if (this.env === 'preview' && typeof this.config.preview.open === 'boolean') {
13✔
124
        bsOptions.open = false
5✔
125
      }
126
    }
127

128
    // Handle by vite so we disable it
129
    if (this.env === 'dev' && typeof bsOptions.codeSync === 'undefined')
19✔
130
      bsOptions.codeSync = false
6✔
131

132
    if (this.mode === 'snippet') {
19✔
133
      // disable log snippet because it is handle by the plugin
134
      bsOptions.logSnippet = false
2✔
135
      bsOptions.snippet = false
2✔
136
    }
137

138
    bsOptions.online
19✔
139
      = bsOptions.online === true
73✔
140
        || (this.server && typeof this.config.server.host !== 'undefined')
141
        || false
142

143
    if (this.env === 'buildWatch')
19✔
144
      return bsOptions
3✔
145

146
    if (this.mode === 'proxy') {
16✔
147
      let target
148

149
      if (this.server?.resolvedUrls?.local[0]) {
15✔
150
        target = this.server?.resolvedUrls?.local[0]
10✔
151
      }
5!
152
      else if (this.port) {
153
        const protocol = this.config.server.https ? 'https' : 'http'
5!
154
        target = `${protocol}://localhost:${this.port}/`
5✔
155
      }
156

157
      if (!bsOptions.proxy) {
15✔
158
        bsOptions.proxy = {
4✔
159
          target,
160
          ws: true,
161
        }
162
      }
11✔
163
      else if (typeof bsOptions.proxy === 'string') {
164
        bsOptions.proxy = {
4✔
165
          target: bsOptions.proxy,
166
          ws: true,
167
        }
168
      }
7✔
169
      else if (
170
        typeof bsOptions.proxy === 'object'
14✔
171
        && !bsOptions.proxy.ws
172
      ) {
173
        bsOptions.proxy.ws = true
2✔
174
      }
175
    }
176

177
    return bsOptions
16✔
178
  }
179

180
  /**
181
   * Init browsersync server
182
   */
183
  private init(): Promise<BrowserSyncInstance> {
184
    return new Promise<BrowserSyncInstance>((resolve, reject) => {
19✔
185
      this.bsServer.init(this.bsOptions, (error, bs) => {
19✔
186
        if (error) {
19!
187
          this.config.logger.error(
×
188
            red(`[vite-plugin-browser-sync] ${error.name} ${error.message}`),
189
            { error },
190
          )
191
          reject(error)
×
192
        }
193
        resolve(bs)
19✔
194
      })
195
    })
196
  }
197

198
  /* c8 ignore start */
199
  /**
200
   * Log browsersync infos
201
   */
202
  private log() {
203
    const colorUrl = (url: string) =>
204
      lightYellow(url.replace(TRAILING_PORT_REGEX, (_, port) => `:${bold(port)}/`))
205

206
    const urls: Record<string, string> = this.bsServer.getOption('urls').toJS()
207
    const consoleTexts: Record<string, string> = {
208
      'local': 'Local',
209
      'external': 'External',
210
      'ui': 'UI',
211
      'ui-external': 'UI External',
212
      'tunnel': 'Tunnel',
213
    }
214
    for (const key in consoleTexts) {
215
      if (Object.hasOwn(consoleTexts, key)) {
216
        const text = consoleTexts[key]
217
        if (Object.hasOwn(urls, key)) {
218
          this.config.logger.info(
219
            `  ${lightYellow('➜')}  ${bold(
220
              `BrowserSync - ${text}`,
221
            )}: ${colorUrl(urls[key])}`,
222
          )
223
        }
224
      }
225
    }
226
  }
227

228
  /**
229
   * Register log function on vite
230
   */
231
  private registerLog() {
232
    if (!this.logged)
233
      return
234

235
    if (this.server && this.env === 'dev') {
236
      // Fix for Astro
237
      let astroServer = false
238
      try {
239
        // Vite 6
240
        astroServer = 'pluginContainer' in this.server
241
          && this.server.environments.client.plugins.findIndex(
242
            plugin => plugin.name === 'astro:server',
243
          ) > -1
244
      }
245
      catch {}
246

247
      if (astroServer) {
248
        setTimeout(() => this.log(), 1000)
249
      }
250
      else {
251
        const _print = this.server.printUrls
252
        this.server.printUrls = () => {
253
          _print()
254
          this.log()
255
        }
256
      }
257
    }
258
    else {
259
      this.log()
260
    }
261
  }
262
  /* c8 ignore stop */
263

264
  /**
265
   * Register init
266
   */
267
  private async registerInit() {
268
    if (this.server && 'listen' in this.server) {
19✔
269
      const _listen = this.server.listen
11✔
270
      this.server.listen = async () => {
11✔
271
        const out = await _listen()
11✔
272
        await this.init()
11✔
273
        return out
11✔
274
      }
275
    }
8✔
276
    else if (this.server) {
277
      await new Promise((resolve) => {
5✔
278
        this.server?.httpServer?.once('listening', () => {
5✔
279
          resolve(true)
5✔
280
        })
281
      })
282
      await this.init()
5✔
283
    }
284
    else {
285
      await this.init()
3✔
286
    }
287
    this.registerLog()
19✔
288
  }
289

290
  /**
291
   * Register close
292
   */
293
  private registerClose() {
294
    if (this.server) {
19✔
295
      const _close = this.server.close
16✔
296
      this.server.close = async () => {
16✔
297
        this.bsServer.exit()
11✔
298
        await _close()
11✔
299
      }
300

301
      this.server.httpServer?.on('close', () => {
16✔
302
        this.bsServer.exit()
16✔
303
      })
304
    }
305

306
    process.once('SIGINT', () => {
19✔
307
      this.bsServer.exit()
×
308
      process.exit()
×
309
    })
310
  }
311
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc