bun_bun_bundle 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53f72b01034723b063b04b4df86acc094b5a84011418ece1c605395f7f181f07
4
- data.tar.gz: a4a1f55347278d192046055421f321d6efb527260a8ea61ff443cc2f178eed86
3
+ metadata.gz: 29e412dc1eeee94ba33c0f47186a649d6a8843b2a4edaf01cf7b72e7f66b29c2
4
+ data.tar.gz: 3744a8ccad103452e0099c6cb8e5bae65ed7a8c4eeb820cad2d52e8accb9384f
5
5
  SHA512:
6
- metadata.gz: 9f70bcf0c97a16e5e8338b32336cebbc410960cf105dcae30bce119ee679c9f9b26b882a88792fb9ae73ebb1de5659de24ba1d38f772271edfe17082b44a02f6
7
- data.tar.gz: 934a76e20b22cb83f947f43c0fa05080000e8b74141a699031f3b8d151625c7874139971c14f8b96952b852e8d2167ce4931de83ba3a7716899601023b310fee
6
+ metadata.gz: 9d9e25a4d96f7a17e5231fdb211e01a231114dc6a3cec1c4c29373ed153896943abb13f5b518c954259d39ffd5d6985058323fa95d9125c1baa8bd4fb8c309e2
7
+ data.tar.gz: 16530cfc8cbbf16e2f56874b9697fcb0b41aa3aa83266116672f516b9778768b0836886a9456dd24c0dae3b1653e6059290b74a7150e5673400c4847653d9afc
data/README.md CHANGED
@@ -174,13 +174,30 @@ bun_bun_bundle dev
174
174
  # Build assets
175
175
  bun_bun_bundle build
176
176
 
177
- # Build with fingerprinting and minification
177
+ # Build with fingerprinting and minification (shortcut)
178
178
  bun_bun_bundle build --prod
179
179
 
180
+ # Fingerprint without minifying (e.g. for staging diagnostics)
181
+ bun_bun_bundle build --fingerprint
182
+
183
+ # Strip sourcemaps from a prod build
184
+ bun_bun_bundle build --prod --sourcemap=none
185
+
180
186
  # Development with verbose WebSocket logging
181
187
  bun_bun_bundle dev --debug
182
188
  ```
183
189
 
190
+ ### Flags
191
+
192
+ - `--prod`: shortcut for `--fingerprint --minify`
193
+ - `--fingerprint`: hash asset filenames for cache busting
194
+ - `--minify`: minify JS and CSS output
195
+ - `--sourcemap[=KIND]`: `inline`, `linked`, `external`, or `none`. Defaults
196
+ to `inline` in `dev` and `linked` for builds, so production stack traces
197
+ and browser devtools stay debuggable. Pass `--sourcemap=none` when you
198
+ explicitly do not want maps shipped.
199
+ - `--debug`: verbose WebSocket logging
200
+
184
201
  > [!NOTE]
185
202
  > When running from a Procfile (e.g. with Overmind or Foreman), use
186
203
  > `bundle exec bun_bun_bundle` to ensure the correct gem version is loaded.
@@ -320,7 +337,11 @@ The context object has the following properties:
320
337
  - `root`: absolute path to the project root
321
338
  - `config`: the resolved `bun.json` configuration object
322
339
  - `dev`: `true` when running in development mode
323
- - `prod`: `true` when running in production mode
340
+ - `prod`: `true` when `--prod` was passed (shortcut flag)
341
+ - `fingerprint`: `true` when asset filenames will be content-hashed
342
+ - `minify`: `true` when output will be minified (use this to strip
343
+ comments, banners, or dev-only branches in your plugin)
344
+ - `sourcemap`: the sourcemap kind being produced (or `null` for default)
324
345
  - `manifest`: the current asset manifest object
325
346
 
326
347
  #### Simple transform plugins
@@ -337,9 +358,9 @@ transform receives the output of the previous one.
337
358
  ```javascript
338
359
  // config/bun/banner.js
339
360
 
340
- export default function banner({prod}) {
361
+ export default function banner({minify}) {
341
362
  return (content, args) => {
342
- const stamp = prod ? '' : ` (dev ${args.path})`
363
+ const stamp = minify ? '' : ` (dev ${args.path})`
343
364
  return `/* My App${stamp} */\n${content}`
344
365
  }
345
366
  }
data/exe/bun_bun_bundle CHANGED
@@ -22,13 +22,19 @@ else
22
22
  build Build assets
23
23
 
24
24
  Flags:
25
- --prod Enable minification and fingerprinting
26
- --debug Enable verbose WebSocket logging
25
+ --prod Shortcut for --fingerprint --minify
26
+ --fingerprint Hash asset filenames for cache busting
27
+ --minify Minify JS and CSS output
28
+ --sourcemap[=KIND] Sourcemap kind: inline, linked, external, none
29
+ (dev defaults to inline, builds default to linked)
30
+ --debug Enable verbose WebSocket logging
27
31
 
28
32
  Examples:
29
- bun_bun_bundle dev # development with live reload
30
- bun_bun_bundle build # build assets
31
- bun_bun_bundle build --prod # build with fingerprinting and minification
33
+ bun_bun_bundle dev # live reload
34
+ bun_bun_bundle build # plain build
35
+ bun_bun_bundle build --prod # fingerprint + minify
36
+ bun_bun_bundle build --fingerprint # fingerprint only
37
+ bun_bun_bundle build --prod --sourcemap=none # strip sourcemaps
32
38
 
33
39
  Configuration:
34
40
  Place a config/bun.json in your project root to customize settings.
data/lib/bun/bake.js CHANGED
@@ -1,9 +1,5 @@
1
1
  import BunBunBundle from "./bun_bundle.js";
2
2
 
3
- BunBunBundle.flags({
4
- debug: process.argv.includes("--debug"),
5
- dev: process.argv.includes("--dev"),
6
- prod: process.argv.includes("--prod"),
7
- });
3
+ BunBunBundle.flags(process.argv);
8
4
 
9
5
  await BunBunBundle.bake();
@@ -21,14 +21,42 @@ export default {
21
21
  debug: false,
22
22
  dev: false,
23
23
  prod: false,
24
+ fingerprint: false,
25
+ minify: false,
26
+ sourcemap: null,
24
27
  wsClients: new Set(),
25
28
  watchTimers: new Map(),
26
29
  plugins: [],
27
30
 
28
- flags({debug, dev, prod}) {
31
+ flags(input) {
32
+ const {debug, dev, prod, fingerprint, minify, sourcemap} =
33
+ Array.isArray(input) ? this.parseArgv(input) : input
29
34
  if (debug != null) this.debug = debug
30
35
  if (dev != null) this.dev = dev
31
36
  if (prod != null) this.prod = prod
37
+ if (fingerprint != null) this.fingerprint = fingerprint
38
+ else if (prod === true) this.fingerprint = true
39
+ if (minify != null) this.minify = minify
40
+ else if (prod === true) this.minify = true
41
+ if (sourcemap != null) this.sourcemap = sourcemap
42
+ },
43
+
44
+ SOURCEMAP_KINDS: ['inline', 'linked', 'external', 'none'],
45
+
46
+ parseArgv(argv) {
47
+ const opts = {}
48
+ if (argv.includes('--debug')) opts.debug = true
49
+ if (argv.includes('--dev')) opts.dev = true
50
+ if (argv.includes('--prod')) opts.prod = true
51
+ if (argv.includes('--fingerprint')) opts.fingerprint = true
52
+ if (argv.includes('--minify')) opts.minify = true
53
+ const sm = argv.find(a => a === '--sourcemap' || a.startsWith('--sourcemap='))
54
+ if (sm) {
55
+ const value = sm.includes('=') ? sm.split('=')[1] : 'linked'
56
+ if (this.SOURCEMAP_KINDS.includes(value)) opts.sourcemap = value
57
+ else console.warn(` ▸ Ignoring --sourcemap=${value} (valid: ${this.SOURCEMAP_KINDS.join(', ')})`)
58
+ }
59
+ return opts
32
60
  },
33
61
 
34
62
  deepMerge(target, source) {
@@ -69,6 +97,9 @@ export default {
69
97
  config: this.config,
70
98
  dev: this.dev,
71
99
  prod: this.prod,
100
+ fingerprint: this.fingerprint,
101
+ minify: this.minify,
102
+ sourcemap: this.sourcemap,
72
103
  manifest: this.manifest
73
104
  })
74
105
  },
@@ -79,8 +110,8 @@ export default {
79
110
  return join(this.root, this.config.outDir)
80
111
  },
81
112
 
82
- fingerprint(name, ext, content) {
83
- if (!this.prod) return `${name}${ext}`
113
+ fingerprintName(name, ext, content) {
114
+ if (!this.fingerprint) return `${name}${ext}`
84
115
 
85
116
  const hash = Bun.hash(content).toString(16).slice(0, 8)
86
117
  return `${name}-${hash}${ext}`
@@ -107,7 +138,7 @@ export default {
107
138
  try {
108
139
  result = await Bun.build({
109
140
  entrypoints: [entryPath],
110
- minify: this.prod,
141
+ minify: this.minify,
111
142
  plugins: this.plugins,
112
143
  ...options
113
144
  })
@@ -124,16 +155,26 @@ export default {
124
155
  continue
125
156
  }
126
157
 
127
- const output = result.outputs.find(o => o.path.endsWith(ext))
128
- if (!output) {
158
+ const mainOutput = result.outputs.find(o => o.path.endsWith(ext))
159
+ if (!mainOutput) {
129
160
  console.error(` ▸ No ${type.toUpperCase()} output for ${entry}`)
130
161
  continue
131
162
  }
163
+ const mapOutput = result.outputs.find(o => o.kind === 'sourcemap')
164
+
165
+ let content = await mainOutput.text()
166
+ const fileName = this.fingerprintName(entryName, ext, content)
167
+
168
+ if (mapOutput) {
169
+ const mapFileName = `${fileName}.map`
170
+ content = content.replace(
171
+ /\/\/# sourceMappingURL=\S+/,
172
+ () => `//# sourceMappingURL=${mapFileName}`
173
+ )
174
+ await Bun.write(join(outDir, mapFileName), await mapOutput.text())
175
+ }
132
176
 
133
- const content = await output.text()
134
- const fileName = this.fingerprint(entryName, ext, content)
135
177
  await Bun.write(join(outDir, fileName), content)
136
-
137
178
  this.manifest[`${type}/${entryName}${ext}`] = `${type}/${fileName}`
138
179
  }
139
180
  },
@@ -142,7 +183,7 @@ export default {
142
183
  await this.buildAssets('js', {
143
184
  target: 'browser',
144
185
  format: 'iife',
145
- sourcemap: this.dev ? 'inline' : 'none'
186
+ sourcemap: this.sourcemap || (this.dev ? 'inline' : 'linked')
146
187
  })
147
188
  },
148
189
 
@@ -166,7 +207,7 @@ export default {
166
207
 
167
208
  const ext = extname(file)
168
209
  const name = file.slice(0, -ext.length) || file
169
- const fileName = this.fingerprint(name, ext, new Uint8Array(content))
210
+ const fileName = this.fingerprintName(name, ext, new Uint8Array(content))
170
211
  const destPath = join(destDir, fileName)
171
212
 
172
213
  mkdirSync(dirname(destPath), {recursive: true})
@@ -1,4 +1,4 @@
1
- import {dirname, extname} from 'path'
1
+ import {dirname, extname, isAbsolute, relative, join} from 'path'
2
2
  import {Glob} from 'bun'
3
3
 
4
4
  const REGEX = /import\s+(\w+)\s+from\s+['"]glob:([^'"]+)['"]/g
@@ -20,24 +20,46 @@ function excluded(file, matchers) {
20
20
 
21
21
  function scanFiles(dir, pattern, excludes) {
22
22
  const {clean, base} = splitBase(pattern)
23
- const matchers = excludes.map(e => new Glob(e.replace(/^\.\//, '')))
24
- const glob = new Glob(clean)
25
- return Array.from(glob.scanSync({cwd: dir}))
26
- .filter(f => !excluded(f, matchers))
27
- .sort()
23
+ const abs = isAbsolute(clean)
24
+ const cwd = abs ? base : dir
25
+ const globPart = abs ? clean.slice(base.length + 1) : clean
26
+ const matchers = excludes.map(e => {
27
+ const ex = e.replace(/^\.\//, '')
28
+ return new Glob(abs && isAbsolute(ex) ? ex.slice(base.length + 1) : ex)
29
+ })
30
+ const glob = new Glob(globPart)
31
+ const files = []
32
+
33
+ for (const file of glob.scanSync({cwd})) {
34
+ if (excluded(file, matchers)) continue
35
+ const absPath = join(cwd, file)
36
+ const rel = relative(dir, absPath)
37
+ files.push(rel)
38
+ }
39
+
40
+ return files.sort()
41
+ }
42
+
43
+ function keyBase(pattern) {
44
+ const {base} = splitBase(pattern)
45
+ if (!isAbsolute(base)) return base
46
+ const last = base.lastIndexOf('/')
47
+ return last > 0 ? base.slice(last + 1) : base
28
48
  }
29
49
 
30
- function buildImportMap(files, base) {
50
+ function buildImportMap(files, dir, base) {
31
51
  const imports = []
32
52
  const entries = []
33
53
 
34
54
  for (const file of files) {
35
55
  const ext = extname(file)
36
- const rel = base ? file.slice(base.length + 1) : file
37
- const key = rel.slice(0, -ext.length)
38
- const prefix = base ? `${base}_` : ''
39
- const safe = `_glob_${prefix}${key}`.replace(/[^a-zA-Z0-9]/g, '_')
40
- imports.push(`import ${safe} from './${file}'`)
56
+ const key = file
57
+ .slice(0, -ext.length)
58
+ .replace(/^[./]+/, '')
59
+ .replace(new RegExp(`^.*${base}/`), '')
60
+ const safe = `_glob_${base}_${key}`.replace(/[^a-zA-Z0-9]/g, '_')
61
+ const rel = file.startsWith('.') ? file : `./${file}`
62
+ imports.push(`import ${safe} from '${rel}'`)
41
63
  entries.push(` '${key}': ${safe}`)
42
64
  }
43
65
 
@@ -48,13 +70,13 @@ export default function jsGlobs() {
48
70
  return (content, args) => {
49
71
  return content.replace(REGEX, (_, binding, raw) => {
50
72
  const {pattern, excludes} = parseImport(raw)
51
- const {base} = splitBase(pattern)
73
+ const base = keyBase(pattern)
52
74
  const dir = dirname(args.path)
53
75
  const files = scanFiles(dir, pattern, excludes)
54
76
 
55
77
  if (!files.length) return `const ${binding} = {}`
56
78
 
57
- const {imports, entries} = buildImportMap(files, base)
79
+ const {imports, entries} = buildImportMap(files, dir, base)
58
80
 
59
81
  return [
60
82
  ...imports,
@@ -33,6 +33,19 @@ module BunBunBundle
33
33
  const ws = new WebSocket('#{config.dev_server.ws_url}')
34
34
  let connected = false
35
35
 
36
+ const scrollKey = 'bun-scroll:' + location.pathname
37
+ addEventListener('load', () => {
38
+ const saved = sessionStorage.getItem(scrollKey)
39
+ if (saved !== null) {
40
+ sessionStorage.removeItem(scrollKey)
41
+ scrollTo(0, parseInt(saved, 10))
42
+ }
43
+ })
44
+ const reload = () => {
45
+ sessionStorage.setItem(scrollKey, String(scrollY))
46
+ location.reload()
47
+ }
48
+
36
49
  ws.onmessage = (event) => {
37
50
  const data = JSON.parse(event.data)
38
51
 
@@ -50,7 +63,7 @@ module BunBunBundle
50
63
  console.error('\\u2716 Build error:', data.message)
51
64
  } else {
52
65
  console.log('\\u25b8 Reloading...')
53
- location.reload()
66
+ reload()
54
67
  }
55
68
  }
56
69
 
@@ -59,7 +72,7 @@ module BunBunBundle
59
72
  console.log('\\u25b8 Live reload connected')
60
73
  }
61
74
  ws.onclose = () => {
62
- if (connected) setTimeout(() => location.reload(), 2000)
75
+ if (connected) setTimeout(reload, 2000)
63
76
  }
64
77
  })()
65
78
  </script>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BunBunBundle
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bun_bun_bundle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wout Fierens