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

Applelo / vite-plugin-browser-sync / 17160834009

22 Aug 2025 04:38PM UTC coverage: 93.017% (-2.2%) from 95.225%
17160834009

push

github

Applelo
disable mac os ci testing

91 of 102 branches covered (89.22%)

Branch coverage included in aggregate %.

242 of 256 relevant lines covered (94.53%)

14.5 hits per line

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

89.83
/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'
2✔
6
import { create } from 'browser-sync'
2✔
7
import { bold, lightYellow, red } from 'kolorist'
2✔
8

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

15
/**
16
 * Hook browsersync server on vite
17
 */
18
export class Server {
2✔
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
2✔
26

27
  constructor(obj: {
2✔
28
    name: string
29
    server?: ViteServer
30
    options?: Options
31
    config: ResolvedConfig
32
    env: Env
33
  }) {
19✔
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
19✔
45

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

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

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

66
    return mode
34✔
67
  }
44✔
68

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

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

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

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

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

115
    if (typeof bsOptions.logLevel === 'undefined')
19✔
116
      bsOptions.logLevel = 'silent'
19✔
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
      }
×
122
      else if (this.env === 'preview' && typeof this.config.preview.open === 'boolean') {
2!
123
        bsOptions.open = false
×
124
      }
×
125
    }
2✔
126

127
    // Handle by vite so we disable it
128
    if (this.env === 'dev' && typeof bsOptions.codeSync === 'undefined')
19✔
129
      bsOptions.codeSync = false
19✔
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
    }
2✔
136

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

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

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

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

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

176
    return bsOptions
16✔
177
  }
19✔
178

179
  /**
180
   * Init browsersync server
181
   */
182
  private init(): Promise<BrowserSyncInstance> {
2✔
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
      })
19✔
194
    })
19✔
195
  }
19✔
196

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

205
    const urls: Record<string, string> = this.bsServer.getOption('urls').toJS()
206
    const consoleTexts: Record<string, string> = {
207
      'local': 'Local',
208
      'external': 'External',
209
      'ui': 'UI',
210
      'ui-external': 'UI External',
211
      'tunnel': 'Tunnel',
212
    }
213
    for (const key in consoleTexts) {
2✔
214
      if (Object.prototype.hasOwnProperty.call(consoleTexts, key)) {
215
        const text = consoleTexts[key]
216
        if (Object.prototype.hasOwnProperty.call(urls, key)) {
2✔
217
          this.config.logger.info(
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() {
2✔
231
    if (!this.logged)
232
      return
4✔
233

234
    if (this.server && this.env === 'dev') {
4✔
235
      // Fix for Astro
236
      let astroServer = false
237
      try {
238
        // Vite 6
239
        astroServer = 'pluginContainer' in this.server
240
          && this.server.environments.client.plugins.findIndex(
241
            plugin => plugin.name === 'astro:server',
2✔
242
          ) > -1
243
      }
2✔
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) {
2✔
254
        setTimeout(() => this.log(), 1000)
255
      }
256
      else {
257
        const _print = this.server.printUrls
258
        this.server.printUrls = () => {
259
          _print()
260
          this.log()
261
        }
262
      }
263
    }
2✔
264
    else {
265
      this.log()
266
    }
267
  }
268
  /* c8 ignore stop */
269

270
  /**
271
   * Register init
272
   */
273
  private async registerInit() {
2✔
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
      }
11✔
281
    }
11✔
282
    else if (this.server) {
8✔
283
      await new Promise((resolve) => {
5✔
284
        this.server?.httpServer?.once('listening', () => {
5✔
285
          resolve(true)
5✔
286
        })
5✔
287
      })
5✔
288
      await this.init()
5✔
289
    }
5✔
290
    else {
3✔
291
      await this.init()
3✔
292
    }
3✔
293
    this.registerLog()
19✔
294
  }
19✔
295

296
  /**
297
   * Register close
298
   */
299
  private registerClose() {
2✔
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
      }
11✔
306

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

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