Skip to content
42 changes: 42 additions & 0 deletions spec/bun/config_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require "../spec_helper"

describe LuckyBun::Config do
it "uses defaults without a config file" do
config = LuckyBun::Config.from_json("{}")

config.dev_server.host.should eq("127.0.0.1")
config.dev_server.port.should eq(3002)
config.dev_server.secure?.should be_false
config.dev_server.ws_url.should eq("ws://127.0.0.1:3002")
config.entry_points.css.should eq(%w[src/css/app.css])
config.entry_points.js.should eq(%w[src/js/app.js])
config.manifest_path.should eq("public/bun-manifest.json")
config.out_dir.should eq("public/assets")
config.public_path.should eq("/assets")
config.static_dirs.should eq(%w[src/images src/fonts])
end

it "accepts string entry points" do
config = LuckyBun::Config.from_json(
%({"entryPoints": {"js": "src/js/app.ts", "css": "src/css/app.css"}})
)

config.entry_points.js.should eq(%w[src/js/app.ts])
config.entry_points.css.should eq(%w[src/css/app.css])
end

it "accepts array entry points" do
config = LuckyBun::Config.from_json(
%({"entryPoints": {"js": ["src/js/app.js", "src/js/admin.js"]}})
)

config.entry_points.js.should eq(%w[src/js/app.js src/js/admin.js])
end

it "supports secure websocket url" do
config = LuckyBun::Config.from_json(%({"devServer": {"secure": true}}))

config.dev_server.ws_protocol.should eq("wss")
config.dev_server.ws_url.should eq("wss://127.0.0.1:3002")
end
end
38 changes: 38 additions & 0 deletions spec/bun/lucky.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ beforeEach(() => {
LuckyBun.manifest = {}
LuckyBun.config = null
LuckyBun.plugins = []
LuckyBun.debug = false
LuckyBun.prod = false
LuckyBun.dev = false
LuckyBun.root = TEST_DIR
Expand Down Expand Up @@ -59,6 +60,9 @@ describe('flags', () => {
LuckyBun.flags({prod: true})
expect(LuckyBun.prod).toBe(true)

LuckyBun.flags({debug: true})
expect(LuckyBun.debug).toBe(true)

LuckyBun.dev = true
LuckyBun.flags({prod: false})
expect(LuckyBun.dev).toBe(true)
Expand Down Expand Up @@ -87,6 +91,7 @@ describe('loadConfig', () => {
test('uses defaults without a config file', () => {
LuckyBun.loadConfig()
expect(LuckyBun.config.outDir).toBe('public/assets')
expect(LuckyBun.config.watchDirs).toEqual(['src/js', 'src/css', 'src/images', 'src/fonts'])
expect(LuckyBun.config.entryPoints.js).toEqual(['src/js/app.js'])
expect(LuckyBun.config.devServer.port).toBe(3002)
expect(LuckyBun.config.plugins).toEqual({
Expand All @@ -109,6 +114,29 @@ describe('loadConfig', () => {
expect(LuckyBun.config.entryPoints.js).toEqual(['src/js/app.js'])
})

test('merges watchDirs from user config', () => {
createFile(
'config/bun.json',
JSON.stringify({watchDirs: ['src/js', 'src/css']})
)

LuckyBun.loadConfig()

expect(LuckyBun.config.watchDirs).toEqual(['src/js', 'src/css'])
})

test('merges listenHost into devServer config', () => {
createFile(
'config/bun.json',
JSON.stringify({devServer: {listenHost: '0.0.0.0'}})
)

LuckyBun.loadConfig()

expect(LuckyBun.config.devServer.listenHost).toBe('0.0.0.0')
expect(LuckyBun.config.devServer.host).toBe('127.0.0.1')
})

test('user can override plugins', () => {
createFile(
'config/bun.json',
Expand Down Expand Up @@ -187,6 +215,16 @@ describe('buildAssets', () => {
expect(LuckyBun.manifest['js/app.js']).toBeUndefined()
})

test('accepts a string entry point', async () => {
await setupProject(
{'src/js/app.js': 'console.log("single")'},
{entryPoints: {js: 'src/js/app.js'}}
)
await LuckyBun.buildJS()

expect(LuckyBun.manifest['js/app.js']).toBe('js/app.js')
})

test('builds multiple JS entry points', async () => {
await buildJS(
{
Expand Down
1 change: 1 addition & 0 deletions src/bun/bake.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import LuckyBun from './lucky.js'

LuckyBun.flags({
debug: process.argv.includes('--debug'),
dev: process.argv.includes('--dev'),
prod: process.argv.includes('--prod')
})
Expand Down
16 changes: 16 additions & 0 deletions src/bun/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,26 @@ module LuckyBun
struct EntryPoints
include JSON::Serializable

@[JSON::Field(converter: LuckyBun::Config::StringOrArray)]
getter js : Array(String) = %w[src/js/app.js]

@[JSON::Field(converter: LuckyBun::Config::StringOrArray)]
getter css : Array(String) = %w[src/css/app.css]
end

module StringOrArray
def self.from_json(pull : JSON::PullParser) : Array(String)
case pull.kind
when .string? then [pull.read_string]
Comment on lines +38 to +40
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever. I was originally thinking it should have been an exception, but I think this actually fits better with the whole "action of least surprise" or whatever that saying is. I originally reached for a string without thinking 😅 so this works great!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured you wouldn't be the first. I would probably walk into that as well at some point😊

else Array(String).new(pull)
end
end

def self.to_json(value : Array(String), json : JSON::Builder) : Nil
value.to_json(json)
end
end

struct DevServer
include JSON::Serializable

Expand Down
33 changes: 23 additions & 10 deletions src/bun/lucky.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ export default {
root: process.cwd(),
config: null,
manifest: {},
debug: false,
dev: false,
prod: false,
wsClients: new Set(),
watchTimers: new Map(),
plugins: [],

flags({dev, prod}) {
flags({debug, dev, prod}) {
if (debug != null) this.debug = debug
if (dev != null) this.dev = dev
if (prod != null) this.prod = prod
},
Expand All @@ -43,6 +45,7 @@ export default {
const defaults = {
entryPoints: {js: ['src/js/app.js'], css: ['src/css/app.css']},
plugins: {css: ['aliases', 'cssGlobs'], js: ['aliases', 'jsGlobs']},
watchDirs: ['src/js', 'src/css', 'src/images', 'src/fonts'],
staticDirs: ['src/images', 'src/fonts'],
outDir: 'public/assets',
publicPath: '/assets',
Expand Down Expand Up @@ -87,7 +90,8 @@ export default {
const outDir = join(this.outDir, type)
mkdirSync(outDir, {recursive: true})

const entries = this.config.entryPoints[type]
const raw = this.config.entryPoints[type]
const entries = Array.isArray(raw) ? raw : [raw]
const ext = `.${type}`

for (const entry of entries) {
Expand Down Expand Up @@ -219,9 +223,7 @@ export default {
},

async watch() {
const srcDir = join(this.root, 'src')

watch(srcDir, {recursive: true}, (event, filename) => {
const handler = (event, filename) => {
if (!filename) return

let normalizedFilename = filename.replace(/\\/g, '/')
Expand Down Expand Up @@ -259,7 +261,16 @@ export default {
if (err.errors) for (const e of err.errors) console.error(e)
}
})()
})
}

for (const dir of this.config.watchDirs) {
const fullDir = join(this.root, dir)
if (!existsSync(fullDir)) {
console.warn(` ▸ Watch directory ${dir} does not exist, skipping...`)
continue
}
watch(fullDir, {recursive: true}, handler)
}

console.log('Beginning to watch your project')
},
Expand All @@ -268,11 +279,13 @@ export default {
await this.build()
await this.watch()

const {host, port, secure} = this.config.devServer
const {host, listenHost, port, secure} = this.config.devServer
const hostname = listenHost || (secure ? '0.0.0.0' : host)
const debug = this.debug
const wsClients = this.wsClients

Bun.serve({
hostname: secure ? '0.0.0.0' : host,
hostname,
port,
fetch(req, server) {
if (server.upgrade(req)) return
Expand All @@ -281,11 +294,11 @@ export default {
websocket: {
open(ws) {
wsClients.add(ws)
console.log(` ▸ Client connected (${wsClients.size})\n\n`)
if (debug) console.log(` ▸ Client connected (${wsClients.size})\n\n`)
},
close(ws) {
wsClients.delete(ws)
console.log(` ▸ Client disconnected (${wsClients.size})\n\n`)
if (debug) console.log(` ▸ Client disconnected (${wsClients.size})\n\n`)
},
message() {}
}
Expand Down
10 changes: 8 additions & 2 deletions src/lucky/tags/bun_reload_tag.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Lucky::BunReloadTag
(() => {
const cssPaths = #{bun_reload_connect_css_files(config).to_json};
const ws = new WebSocket('#{config.dev_server.ws_url}')
let connected = false

ws.onmessage = (event) => {
const data = JSON.parse(event.data)
Expand All @@ -35,8 +36,13 @@ module Lucky::BunReloadTag
}
}

ws.onopen = () => console.log('▸ Live reload connected')
ws.onclose = () => setTimeout(() => location.reload(), 2000)
ws.onopen = () => {
connected = true
console.log('▸ Live reload connected')
}
ws.onclose = () => {
if (connected) setTimeout(() => location.reload(), 2000)
}
})()
JS
end
Expand Down
Loading