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

Applelo / vite-plugin-browser-sync / 19079512263

04 Nov 2025 06:50PM UTC coverage: 92.748% (-0.3%) from 93.017%
19079512263

Pull #29

github

web-flow
Merge 0b6e8ac80 into e5345d53d
Pull Request #29: Version 6.0.0

111 of 122 branches covered (90.98%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

132 of 140 relevant lines covered (94.29%)

16.41 hits per line

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

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

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

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

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

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

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

62
    if (this.userBsOptions.proxy)
44✔
63
      mode = 'proxy'
25✔
64

65
    return mode
34✔
66
  }
67

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

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

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

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

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

114
    if (typeof bsOptions.logLevel === 'undefined')
19✔
115
      bsOptions.logLevel = 'silent'
14✔
116

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

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

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

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

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

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

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

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

175
    return bsOptions
16✔
176
  }
177

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

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

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

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

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

245
      if (astroServer) {
6!
UNCOV
246
        setTimeout(() => this.log(), 1000)
×
247
      }
248
      else {
249
        const _print = this.server.printUrls
6✔
250
        this.server.printUrls = () => {
6✔
UNCOV
251
          _print()
×
UNCOV
252
          this.log()
×
253
        }
254
      }
255
    }
256
    else {
257
      this.log()
8✔
258
    }
259
  }
260
  /* c8 ignore stop */
261

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

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

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

304
    process.once('SIGINT', () => {
19✔
305
      this.bsServer.exit()
×
306
      process.exit()
×
307
    })
308
  }
309
}
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