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

Applelo / vite-plugin-browser-sync / 19049001548

03 Nov 2025 08:53PM UTC coverage: 89.85% (-3.2%) from 93.017%
19049001548

Pull #29

github

web-flow
Merge 0d3195a92 into e5345d53d
Pull Request #29: Version 5.0.1

109 of 124 branches covered (87.9%)

Branch coverage included in aggregate %.

130 of 142 relevant lines covered (91.55%)

16.1 hits per line

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

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

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

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

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

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

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

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

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

63
    if (this.userBsOptions.proxy)
44✔
64
      mode = 'proxy'
19✔
65

66
    return mode
34✔
67
  }
68

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

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

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

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

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

115
    if (typeof bsOptions.logLevel === 'undefined')
19✔
116
      bsOptions.logLevel = 'silent'
16✔
117

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

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

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

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

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

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

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

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

176
    return bsOptions
16✔
177
  }
178

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

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

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

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

234
    if (this.server && this.env === 'dev') {
16✔
235
      // Fix for Astro
236
      let astroServer = false
8✔
237
      try {
8✔
238
        // Vite 6
239
        astroServer = 'pluginContainer' in this.server
8✔
240
          && this.server.environments.client.plugins.findIndex(
241
            plugin => plugin.name === 'astro:server',
192✔
242
          ) > -1
243
      }
244
      catch {
245
        // Vite 5
246
        astroServer = 'pluginContainer' in this.server
×
247
        // @ts-expect-error Vite 5 support
248
          && this.server.pluginContainer.plugins.findIndex(
249
            (plugin: OutputPlugin) => plugin.name === 'astro:server',
×
250
          ) > -1
251
      }
252

253
      if (astroServer) {
8!
254
        setTimeout(() => this.log(), 1000)
×
255
      }
256
      else {
257
        const _print = this.server.printUrls
8✔
258
        this.server.printUrls = () => {
8✔
259
          _print()
×
260
          this.log()
×
261
        }
262
      }
263
    }
264
    else {
265
      this.log()
8✔
266
    }
267
  }
268
  /* c8 ignore stop */
269

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

296
  /**
297
   * Register close
298
   */
299
  private registerClose() {
300
    if (this.server) {
19✔
301
      const _close = this.server.close
16✔
302
      this.server.close = async () => {
16✔
303
        this.bsServer.exit()
11✔
304
        await _close()
11✔
305
      }
306

307
      this.server.httpServer?.on('close', () => {
16✔
308
        this.bsServer.exit()
16✔
309
      })
310
    }
311

312
    process.once('SIGINT', () => {
19✔
313
      this.bsServer.exit()
×
314
      process.exit()
×
315
    })
316
  }
317
}
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