diff --git a/bun.lock b/bun.lock index 2e6a85a..0c1d684 100644 --- a/bun.lock +++ b/bun.lock @@ -4,14 +4,19 @@ "": { "name": "bun-next", "dependencies": { + "@tailwindcss/postcss": "^4.2.1", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "commander": "^14.0.2", + "esbuild": "^0.27.4", + "lodash": "^4.17.23", "micromatch": "^4.0.8", "ora": "^9.0.0", + "postcss": "^8.5.8", }, "devDependencies": { "@types/bun": "latest", + "@types/lodash": "^4.17.24", "@types/micromatch": "^4.0.10", "@types/node": "^24.10.0", "@types/react": "^19.2.2", @@ -27,6 +32,70 @@ }, }, "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PXgg5gqcS/rHwa1hF0JdM1y5TiyejVrMHoBmWY/DjtfYZoFTXie1RCFOkoG0b5diOOmUcuYarMpH7CSNTqwj+w=="], "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Nhssuh7GBpP5PiDSOl3+qnoIG7PJo+ec2oomDevnl9pRY6x6aD2gRt0JE+uf+A8Om2D6gjeHCxjEdrw5ZHE8mA=="], @@ -51,10 +120,42 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-gh3UAHbUdDUG6fhLc1Csa4IGdtghue6U8oAIXWnUqawp6lwb3gOCRvp25IUnLF5vUHtgfMxuEUYV7YA2WxVutw=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="], + "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], "@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="], + "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="], + "@types/micromatch": ["@types/micromatch@4.0.10", "", { "dependencies": { "@types/braces": "*" } }, "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ=="], "@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="], @@ -85,28 +186,72 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], + + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="], + + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "ora": ["ora@9.0.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0", "strip-ansi": "^7.1.2" } }, "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -117,12 +262,18 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], @@ -131,6 +282,18 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/ws/@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], "bun-types/@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], diff --git a/bunext.config.ts b/bunext.config.ts new file mode 100644 index 0000000..e9e8d50 --- /dev/null +++ b/bunext.config.ts @@ -0,0 +1,2 @@ +const config = {}; +export default config; diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..55db1c4 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[serve.static] +plugins = ["bun-plugin-tailwind"] \ No newline at end of file diff --git a/commands/build/index.ts b/commands/build/index.ts index 1d7d691..f4eac22 100644 --- a/commands/build/index.ts +++ b/commands/build/index.ts @@ -1,9 +1,7 @@ import { Command } from "commander"; import grabConfig from "../../src/functions/grab-config"; -import startServer from "../../src/functions/server/start-server"; import init from "../../src/functions/init"; import type { BunextConfig } from "../../src/types"; -import grabAllPages from "../../src/utils/grab-all-pages"; import allPagesBundler from "../../src/functions/bundler/all-pages-bundler"; export default function () { @@ -12,6 +10,8 @@ export default function () { .action(async () => { console.log(`Building Project ...`); + process.env.NODE_ENV = "production"; + await init(); const config: BunextConfig = (await grabConfig()) || {}; @@ -21,6 +21,8 @@ export default function () { development: true, }; - allPagesBundler(); + allPagesBundler({ + exit_after_first_build: true, + }); }); } diff --git a/index.ts b/index.ts index 4166d6a..d33da33 100755 --- a/index.ts +++ b/index.ts @@ -4,11 +4,16 @@ import { program } from "commander"; import start from "./commands/start"; import dev from "./commands/dev"; import ora, { type Ora } from "ora"; -import type { BunextConfig } from "./src/types"; +import type { + BundlerCTXMap, + BunextConfig, + GlobalHMRControllerObject, +} from "./src/types"; import type { FileSystemRouter, Server } from "bun"; import init from "./src/functions/init"; import grabDirNames from "./src/utils/grab-dir-names"; import build from "./commands/build"; +import type { BuildContext, BuildResult } from "esbuild"; /** * # Declare Global Variables @@ -20,13 +25,17 @@ declare global { var RECOMPILING: boolean; var WATCHER_TIMEOUT: any; var ROUTER: FileSystemRouter; - var HMR_CONTROLLERS: Set>; + var HMR_CONTROLLERS: GlobalHMRControllerObject[]; var LAST_BUILD_TIME: number; + var BUNDLER_CTX: BuildContext | undefined; + var BUNDLER_CTX_MAP: BundlerCTXMap[] | undefined; + var IS_FIRST_BUNDLE_READY: boolean; } global.ORA_SPINNER = ora(); global.ORA_SPINNER.clear(); -global.HMR_CONTROLLERS = new Set(); +global.HMR_CONTROLLERS = []; +global.IS_FIRST_BUNDLE_READY = false; await init(); diff --git a/package.json b/package.json index 85e3652..b482427 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@types/bun": "latest", + "@types/lodash": "^4.17.24", "@types/micromatch": "^4.0.10", "@types/node": "^24.10.0", "@types/react": "^19.2.2", @@ -31,10 +32,14 @@ "react-dom": "^19.0.0" }, "dependencies": { + "@tailwindcss/postcss": "^4.2.1", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "commander": "^14.0.2", + "esbuild": "^0.27.4", + "lodash": "^4.17.23", "micromatch": "^4.0.8", - "ora": "^9.0.0" + "ora": "^9.0.0", + "postcss": "^8.5.8" } } diff --git a/src/build/build.ts b/src/build/build.ts index f7cb11d..c7de0d7 100644 --- a/src/build/build.ts +++ b/src/build/build.ts @@ -1,4 +1,5 @@ #!/usr/bin/env bun +import type { BuildConfig } from "bun"; import plugin from "bun-plugin-tailwind"; import { existsSync } from "fs"; import { rm } from "fs/promises"; @@ -48,8 +49,8 @@ const parseValue = (value: string): any => { return value; }; -function parseArgs(): Partial { - const config: Partial = {}; +function parseArgs(): Partial { + const config: any = {}; const args = process.argv.slice(2); for (let i = 0; i < args.length; i++) { diff --git a/src/functions/bundler/all-pages-bundler.ts b/src/functions/bundler/all-pages-bundler.ts index 53afacd..f8cb9d5 100644 --- a/src/functions/bundler/all-pages-bundler.ts +++ b/src/functions/bundler/all-pages-bundler.ts @@ -1,101 +1,326 @@ -import plugin from "bun-plugin-tailwind"; -import { readdirSync, statSync, unlinkSync } from "fs"; +import { existsSync, writeFileSync } from "fs"; +import path from "path"; +import * as esbuild from "esbuild"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import { readFile } from "fs/promises"; import grabAllPages from "../../utils/grab-all-pages"; import grabDirNames from "../../utils/grab-dir-names"; -import grabPageName from "../../utils/grab-page-name"; -import writeWebPageHydrationScript from "../server/web-pages/write-web-page-hydration-script"; -import path from "path"; -import bundle from "../../utils/bundle"; import AppNames from "../../utils/grab-app-names"; -import type { PageFiles } from "../../types"; import isDevelopment from "../../utils/is-development"; +import type { BundlerCTXMap } from "../../types"; +import { execSync } from "child_process"; +import grabConstants from "../../utils/grab-constants"; -const { BUNX_HYDRATION_SRC_DIR, HYDRATION_DST_DIR } = grabDirNames(); +const { HYDRATION_DST_DIR, PAGES_DIR } = grabDirNames(); -export default async function allPagesBundler() { - console.time("build"); +const tailwindPlugin: esbuild.Plugin = { + name: "tailwindcss", + setup(build) { + build.onLoad({ filter: /\.css$/ }, async (args) => { + const source = await readFile(args.path, "utf-8"); + const result = await postcss([tailwindcss()]).process(source, { + from: args.path, + }); + return { + contents: result.css, + loader: "css", + }; + }); + }, +}; + +type Params = { + watch?: boolean; + exit_after_first_build?: boolean; + post_build_fn?: (params: { artifacts: BundlerCTXMap[] }) => Promise; +}; + +export default async function allPagesBundler(params?: Params) { const pages = grabAllPages({ exclude_api: true }); + const { ClientRootElementIDName, ClientRootComponentWindowName } = + await grabConstants(); - for (let i = 0; i < pages.length; i++) { - const page = pages[i]; + const virtualEntries: Record = {}; + const dev = isDevelopment(); - if (!isPageValid(page)) { - continue; + const root_component_path = path.join( + PAGES_DIR, + `${AppNames["RootPagesComponentName"]}.tsx`, + ); + + const does_root_exist = existsSync(root_component_path); + + for (const page of pages) { + const key = page.local_path; + + let txt = ``; + txt += `import { hydrateRoot } from "react-dom/client";\n`; + if (does_root_exist) { + txt += `import Root from "${root_component_path}";\n`; } + txt += `import Page from "${page.local_path}";\n\n`; + txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`; - const pageName = grabPageName({ path: page.local_path }); + if (does_root_exist) { + txt += `const component = \n`; + } else { + txt += `const component = \n`; + } + txt += `const root = hydrateRoot(document.getElementById("${ClientRootElementIDName}"), component);\n\n`; + txt += `window.${ClientRootComponentWindowName} = root;\n`; - writeWebPageHydrationScript({ - pageName, - page_file: page.local_path, - }); + virtualEntries[key] = txt; } - const hydration_files = readdirSync(BUNX_HYDRATION_SRC_DIR); + const virtualPlugin: esbuild.Plugin = { + name: "virtual-entrypoints", + setup(build) { + build.onResolve({ filter: /^virtual:/ }, (args) => ({ + path: args.path.replace("virtual:", ""), + namespace: "virtual", + })); - for (let i = 0; i < hydration_files.length; i++) { - const hydration_file = hydration_files[i]; + build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({ + contents: virtualEntries[args.path], + loader: "tsx", + resolveDir: process.cwd(), + })); + }, + }; - const valid_file = pages.find((p) => { - if (!isPageValid(p)) { - return false; - } + const artifactTracker: esbuild.Plugin = { + name: "artifact-tracker", + setup(build) { + build.onStart(() => { + console.time("build"); + }); - const pageName = grabPageName({ path: p.local_path }); + build.onEnd((result) => { + if (result.errors.length > 0) return; - const file_tsx_name = `${pageName}.tsx`; - if (file_tsx_name == hydration_file) { - return true; - } - return false; - }); + const artifacts: (BundlerCTXMap | undefined)[] = Object.entries( + result.metafile!.outputs, + ) + .filter(([, meta]) => meta.entryPoint) + .map(([outputPath, meta]) => { + const target_page = pages.find((p) => { + return ( + meta.entryPoint === `virtual:${p.local_path}` + ); + }); - if (!valid_file) { - unlinkSync(path.join(BUNX_HYDRATION_SRC_DIR, hydration_file)); - } - } + if (!target_page || !meta.entryPoint) { + return undefined; + } - const entrypoints = readdirSync(BUNX_HYDRATION_SRC_DIR) - .filter((f) => f.endsWith(".tsx")) - .map((f) => path.join(BUNX_HYDRATION_SRC_DIR, f)) - .filter((f) => statSync(f).isFile()); + const { file_name, local_path, url_path } = target_page; - bundle({ - src: entrypoints.join(" "), - out_dir: HYDRATION_DST_DIR, - exec_options: { stdio: "ignore" }, + const cssPath = meta.cssBundle || undefined; + + return { + path: outputPath, + hash: path.basename( + outputPath, + path.extname(outputPath), + ), + type: outputPath.endsWith(".css") + ? "text/css" + : "text/javascript", + entrypoint: meta.entryPoint, + css_path: cssPath, + file_name, + local_path, + url_path, + }; + }); + + if (artifacts.length > 0) { + const final_artifacts = artifacts.filter((a) => + Boolean(a?.entrypoint), + ) as BundlerCTXMap[]; + // writeFileSync( + // HYDRATION_DST_DIR_MAP_JSON_FILE, + // JSON.stringify(final_artifacts), + // ); + + global.BUNDLER_CTX_MAP = final_artifacts; + params?.post_build_fn?.({ artifacts: final_artifacts }); + } + + console.timeEnd("build"); + + if (params?.exit_after_first_build) { + console.log( + "global.BUNDLER_CTX_MAP", + global.BUNDLER_CTX_MAP, + ); + process.exit(); + } + }); + }, + }; + + execSync(`rm -rf ${HYDRATION_DST_DIR}`); + + const ctx = await esbuild.context({ + entryPoints: Object.keys(virtualEntries).map((k) => `virtual:${k}`), + outdir: HYDRATION_DST_DIR, + bundle: true, + minify: !dev, + format: "esm", + target: "es2020", + platform: "browser", + define: { + "process.env.NODE_ENV": JSON.stringify( + dev ? "development" : "production", + ), + }, + entryNames: "[dir]/[name]/[hash]", + // entryNames: "[name]/[hash]", + metafile: true, + plugins: [tailwindPlugin, virtualPlugin, artifactTracker], + jsx: "automatic", }); - // console.log(`Bundling ...`); + await ctx.rebuild(); - // const result = await Bun.build({ - // entrypoints, - // outdir: HYDRATION_DST_DIR, - // plugins: [plugin], - // minify: true, - // target: "browser", - // // sourcemap: "linked", - // define: { - // "process.env.NODE_ENV": JSON.stringify( - // isDevelopment() ? "development" : "production", - // ), - // }, - // }); - - // console.log("result", result); - - console.timeEnd("build"); + if (params?.watch) { + global.BUNDLER_CTX = ctx; + global.BUNDLER_CTX.watch(); + } } -function isPageValid(page: PageFiles): boolean { - if (page.file_name == AppNames["RootPagesComponentName"]) { - return false; - } +// import plugin from "bun-plugin-tailwind"; +// import { readdirSync, statSync, unlinkSync, writeFileSync } from "fs"; +// import grabAllPages from "../../utils/grab-all-pages"; +// import grabDirNames from "../../utils/grab-dir-names"; +// import grabPageName from "../../utils/grab-page-name"; +// import writeWebPageHydrationScript from "../server/web-pages/write-web-page-hydration-script"; +// import path from "path"; +// import bundle from "../../utils/bundle"; +// import AppNames from "../../utils/grab-app-names"; +// import type { PageFiles } from "../../types"; +// import isDevelopment from "../../utils/is-development"; +// import { execSync } from "child_process"; - if (page.url_path.match(/\(|\)|--/)) { - return false; - } +// const { +// BUNX_HYDRATION_SRC_DIR, +// HYDRATION_DST_DIR, +// HYDRATION_DST_DIR_MAP_JSON_FILE, +// } = grabDirNames(); - return true; -} +// export default async function allPagesBundler() { +// console.time("build"); + +// const pages = grabAllPages({ exclude_api: true }); + +// for (let i = 0; i < pages.length; i++) { +// const page = pages[i]; + +// if (!isPageValid(page)) { +// continue; +// } + +// const pageName = grabPageName({ path: page.local_path }); + +// writeWebPageHydrationScript({ +// pageName, +// page_file: page.local_path, +// }); +// } + +// // const hydration_files = readdirSync(BUNX_HYDRATION_SRC_DIR); + +// // for (let i = 0; i < hydration_files.length; i++) { +// // const hydration_file = hydration_files[i]; + +// // const valid_file = pages.find((p) => { +// // if (!isPageValid(p)) { +// // return false; +// // } + +// // const pageName = grabPageName({ path: p.local_path }); + +// // const file_tsx_name = `${pageName}.tsx`; +// // if (file_tsx_name == hydration_file) { +// // return true; +// // } +// // return false; +// // }); + +// // if (!valid_file) { +// // unlinkSync(path.join(BUNX_HYDRATION_SRC_DIR, hydration_file)); +// // } +// // } + +// // const entrypoints = readdirSync(BUNX_HYDRATION_SRC_DIR) +// // .filter((f) => f.endsWith(".tsx")) +// // .map((f) => path.join(BUNX_HYDRATION_SRC_DIR, f)) +// // .filter((f) => statSync(f).isFile()); + +// const entrypoints = pages.map((p) => p.local_path); + +// // execSync(`rm -rf ${HYDRATION_DST_DIR}`); + +// // bundle({ +// // src: entrypoints.join(" "), +// // out_dir: HYDRATION_DST_DIR, +// // exec_options: { stdio: "ignore" }, +// // entry_naming: `[dir]/[name]/[hash].js`, +// // minify: true, +// // target: "browser", +// // }); + +// // console.log(`Bundling ...`); + +// const result = await Bun.build({ +// entrypoints, +// outdir: HYDRATION_DST_DIR, +// plugins: [plugin], +// minify: true, +// target: "browser", +// // sourcemap: "linked", +// define: { +// "process.env.NODE_ENV": JSON.stringify( +// isDevelopment() ? "development" : "production", +// ), +// }, +// naming: "[dir]/[name]/[hash].js", +// }); + +// const artifacts = result.outputs.map(({ path, hash, type }) => { +// const target_page = pages.find((p) => +// p.local_path.replace(/src\/pages/, "public/pages"), +// ); + +// return { +// path, +// hash, +// type, +// ...target_page, +// }; +// }); + +// if (artifacts?.[0]) { +// writeFileSync( +// HYDRATION_DST_DIR_MAP_JSON_FILE, +// JSON.stringify(artifacts), +// ); +// } + +// console.timeEnd("build"); +// } + +// function isPageValid(page: PageFiles): boolean { +// if (page.file_name == AppNames["RootPagesComponentName"]) { +// return false; +// } + +// if (page.url_path.match(/\(|\)|--/)) { +// return false; +// } + +// return true; +// } diff --git a/src/functions/router/get-route.ts b/src/functions/router/get-route.ts deleted file mode 100644 index a5fee83..0000000 --- a/src/functions/router/get-route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import grabDirNames from "../../utils/grab-dir-names"; -import type { GetRouteReturn } from "../../types"; -import grabAssetsPrefix from "../../utils/grab-assets-prefix"; -import grabOrigin from "../../utils/grab-origin"; -import grabRouter from "../../utils/grab-router"; - -type Params = { - route: string; -}; - -export default async function getRoute({ - route, -}: Params): Promise { - const {} = grabDirNames(); - - if (route.match(/\(/)) { - return null; - } - - const router = grabRouter(); - - const match = router.match(route); - - if (!match?.filePath) { - console.error(`Route ${route} not found`); - return null; - } - - const module = await import(match.filePath); - - return { - match, - module, - component: module.default, - serverProps: module.serverProps, - staticProps: module.staticProps, - staticPaths: module.staticPaths, - staticParams: module.staticParams, - }; -} diff --git a/src/functions/server/handle-routes.ts b/src/functions/server/handle-routes.ts index 4315f31..2d82811 100644 --- a/src/functions/server/handle-routes.ts +++ b/src/functions/server/handle-routes.ts @@ -28,8 +28,7 @@ export default async function ({ if (!match?.filePath) { const errMsg = `Route ${url.pathname} not found`; - - console.error(errMsg); + // console.error(errMsg); return { success: false, diff --git a/src/functions/server/server-params-gen.ts b/src/functions/server/server-params-gen.ts index caea2f6..ce8a585 100644 --- a/src/functions/server/server-params-gen.ts +++ b/src/functions/server/server-params-gen.ts @@ -1,33 +1,114 @@ import path from "path"; -import type { ServeOptions } from "bun"; +import type { RouterTypes, ServeOptions } from "bun"; import grabAppPort from "../../utils/grab-app-port"; import grabDirNames from "../../utils/grab-dir-names"; import handleWebPages from "./web-pages/handle-web-pages"; import handleRoutes from "./handle-routes"; import isDevelopment from "../../utils/is-development"; +import type { Server } from "http"; type Params = { dev?: boolean; }; +// type ServerOptions = Omit & { +// routes: { [K: string]: RouterTypes.RouteValue }; +// fetch?: ( +// this: Server, +// request: Request, +// server: Server, +// ) => Response | Promise; +// }; + export default async function (params?: Params): Promise { const port = grabAppPort(); const { PUBLIC_DIR } = grabDirNames(); + // const opts: ServerOptions = { + // routes: { + // "/__hmr": { + // async GET(req) { + // if (!isDevelopment()) { + // return new Response(`Production Environment`); + // } + + // let controller: ReadableStreamDefaultController; + // const stream = new ReadableStream({ + // start(c) { + // controller = c; + // global.HMR_CONTROLLERS.add(c); + // }, + // cancel() { + // global.HMR_CONTROLLERS.delete(controller); + // }, + // }); + + // return new Response(stream, { + // headers: { + // "Content-Type": "text/event-stream", + // "Cache-Control": "no-cache", + // Connection: "keep-alive", + // }, + // }); + // }, + // }, + // "/api/*": {}, + // "/*": { + // async GET(req) { + // return await handleWebPages({ req }); + // }, + // }, + // }, + // }; + return { async fetch(req, server) { try { const url = new URL(req.url); if (url.pathname === "/__hmr" && isDevelopment()) { + const referer_url = new URL( + req.headers.get("referer") || "", + ); + const match = global.ROUTER.match(referer_url.pathname); + + if (!match?.filePath) { + return new Response(`Unhandled Path.`); + } + + const target_map = global.BUNDLER_CTX_MAP?.find( + (m) => m.local_path == match.filePath, + ); + + if (!target_map?.entrypoint) { + return new Response(`Target Path has no map`); + } + let controller: ReadableStreamDefaultController; const stream = new ReadableStream({ start(c) { controller = c; - global.HMR_CONTROLLERS.add(c); + global.HMR_CONTROLLERS.push({ + controller: c, + page_url: referer_url.href, + target_map, + }); }, cancel() { - global.HMR_CONTROLLERS.delete(controller); + const targetControllerIndex = + global.HMR_CONTROLLERS.findIndex( + (c) => c.controller == controller, + ); + + if ( + typeof targetControllerIndex == "number" && + targetControllerIndex >= 0 + ) { + global.HMR_CONTROLLERS.splice( + targetControllerIndex, + 1, + ); + } }, }); @@ -38,7 +119,9 @@ export default async function (params?: Params): Promise { Connection: "keep-alive", }, }); - } else if (url.pathname.startsWith("/api/")) { + } + + if (url.pathname.startsWith("/api/")) { const res = await handleRoutes({ req, server }); return new Response(JSON.stringify(res), { @@ -47,7 +130,9 @@ export default async function (params?: Params): Promise { "Content-Type": "application/json", }, }); - } else if (url.pathname.startsWith("/public/")) { + } + + if (url.pathname.startsWith("/public/")) { const file = Bun.file( path.join( PUBLIC_DIR, @@ -56,13 +141,15 @@ export default async function (params?: Params): Promise { ); return new Response(file); - } else if (url.pathname.startsWith("/favicon.")) { + } + + if (url.pathname.startsWith("/favicon.")) { const file = Bun.file(path.join(PUBLIC_DIR, url.pathname)); return new Response(file); - } else { - return await handleWebPages({ req }); } + + return await handleWebPages({ req }); } catch (error: any) { return new Response(`Server Error: ${error.message}`, { status: 500, @@ -71,5 +158,8 @@ export default async function (params?: Params): Promise { }, port, idleTimeout: 0, + development: { + hmr: true, + }, } as ServeOptions; } diff --git a/src/functions/server/server-post-build-fn.ts b/src/functions/server/server-post-build-fn.ts new file mode 100644 index 0000000..672cfae --- /dev/null +++ b/src/functions/server/server-post-build-fn.ts @@ -0,0 +1,38 @@ +import _ from "lodash"; +import type { BundlerCTXMap, GlobalHMRControllerObject } from "../../types"; + +type Params = { + artifacts: BundlerCTXMap[]; +}; + +export default async function serverPostBuildFn({ artifacts }: Params) { + if (!global.IS_FIRST_BUNDLE_READY) { + global.IS_FIRST_BUNDLE_READY = true; + } + + if (!global.HMR_CONTROLLERS?.[0]) { + return; + } + + for (let i = 0; i < global.HMR_CONTROLLERS.length; i++) { + const controller = global.HMR_CONTROLLERS[i]; + const target_artifact = artifacts.find( + (a) => controller.target_map.local_path == a.local_path, + ); + + if (!target_artifact?.local_path) continue; + + const final_artifact: Omit = { + ..._.omit(controller, ["controller"]), + target_map: target_artifact, + }; + + try { + controller.controller.enqueue( + `event: update\ndata: ${JSON.stringify(final_artifact)}\n\n`, + ); + } catch { + global.HMR_CONTROLLERS.splice(i, 1); + } + } +} diff --git a/src/functions/server/start-server.ts b/src/functions/server/start-server.ts index fc2155c..4049564 100644 --- a/src/functions/server/start-server.ts +++ b/src/functions/server/start-server.ts @@ -1,7 +1,9 @@ +import _ from "lodash"; import AppNames from "../../utils/grab-app-names"; import allPagesBundler from "../bundler/all-pages-bundler"; import serverParamsGen from "./server-params-gen"; import watcher from "./watcher"; +import serverPostBuildFn from "./server-post-build-fn"; type Params = { dev?: boolean; @@ -12,19 +14,35 @@ export default async function startServer(params?: Params) { const serverParams = await serverParamsGen(); + if (params?.dev) { + await allPagesBundler({ + watch: true, + post_build_fn: serverPostBuildFn, + }); + watcher(); + } else { + global.IS_FIRST_BUNDLE_READY = true; + } + + let bundle_ready_retries = 0; + const MAX_BUNDLE_READY_RETRIES = 10; + + while (!global.IS_FIRST_BUNDLE_READY) { + if (bundle_ready_retries > MAX_BUNDLE_READY_RETRIES) { + console.error(`Couldn't grab first bundle for dev environment`); + process.exit(1); + } + bundle_ready_retries++; + await Bun.sleep(500); + } + const server = Bun.serve(serverParams); global.SERVER = server; - await allPagesBundler(); - console.log( `${name} Server Running on http://localhost:${server.port} ...`, ); - if (params?.dev) { - watcher(); - } - return server; } diff --git a/src/functions/server/watcher.tsx b/src/functions/server/watcher.tsx index eb1be9b..c4d4ed3 100644 --- a/src/functions/server/watcher.tsx +++ b/src/functions/server/watcher.tsx @@ -1,14 +1,13 @@ import { watch } from "fs"; import grabDirNames from "../../utils/grab-dir-names"; import serverParamsGen from "./server-params-gen"; -import allPagesBundler from "../bundler/all-pages-bundler"; +// import allPagesBundler from "../bundler/all-pages-bundler"; -const { ROOT_DIR, BUNX_HYDRATION_SRC_DIR, HYDRATION_DST_DIR, PAGES_DIR } = - grabDirNames(); +const { PAGES_DIR } = grabDirNames(); export default function watcher() { watch( - ROOT_DIR, + PAGES_DIR, { recursive: true, persistent: true, @@ -20,6 +19,8 @@ export default function watcher() { // if (filename.match(/\.bunext|\/?public\//)) return; // if (!filename.match(/\.(tsx|ts|css|js|jsx)$/)) return; + console.log("event", event); + if (global.RECOMPILING) return; clearTimeout(global.WATCHER_TIMEOUT); @@ -29,19 +30,19 @@ export default function watcher() { console.log(`File Changed. Rebuilding ...`); - await allPagesBundler(); + // await allPagesBundler(); - global.LAST_BUILD_TIME = Date.now(); + // global.LAST_BUILD_TIME = Date.now(); - for (const controller of global.HMR_CONTROLLERS) { - try { - controller.enqueue( - `event: update\ndata: ${global.LAST_BUILD_TIME}\n\n`, - ); - } catch { - global.HMR_CONTROLLERS.delete(controller); - } - } + // for (const controller of global.HMR_CONTROLLERS) { + // try { + // controller.enqueue( + // `event: update\ndata: ${global.LAST_BUILD_TIME}\n\n`, + // ); + // } catch { + // global.HMR_CONTROLLERS.delete(controller); + // } + // } } catch (error: any) { console.log(error); } finally { diff --git a/src/functions/server/web-pages/generate-web-html.tsx b/src/functions/server/web-pages/generate-web-html.tsx index eff7967..f7bebb6 100644 --- a/src/functions/server/web-pages/generate-web-html.tsx +++ b/src/functions/server/web-pages/generate-web-html.tsx @@ -4,12 +4,12 @@ import grabDirNames from "../../../utils/grab-dir-names"; import EJSON from "../../../utils/ejson"; import type { LivePageDistGenParams } from "../../../types"; import isDevelopment from "../../../utils/is-development"; +import grabWebPageHydrationScript from "./grab-web-page-hydration-script"; export default async function genWebHTML({ component, pageProps, - pageName, - module, + bundledMap, }: LivePageDistGenParams) { const { ClientRootElementIDName, ClientWindowPagePropsName } = await grabContants(); @@ -20,38 +20,31 @@ export default async function genWebHTML({ const componentHTML = renderToString(component); - const SCRIPT_SRC = path.join("/public/pages", pageName + ".js"); - const CSS_SRC = path.join("/public/pages", pageName + ".css"); - const { HYDRATION_DST_DIR } = grabDirNames(); - const cssExists = await Bun.file( - path.join(HYDRATION_DST_DIR, pageName + ".css"), - ).exists(); + // const SCRIPT_SRC = path.join("/public/pages", bundledMap.path); + // const CSS_SRC = bundledMap.css_path + // ? path.join("/public/pages", bundledMap.css_path) + // : undefined; + // const { HYDRATION_DST_DIR } = grabDirNames(); let html = `\n`; html += `\n`; html += ` \n`; html += ` \n`; - if (cssExists) { - html += ` \n`; + if (bundledMap.css_path) { + html += ` \n`; } - // if (isDevelopment()) { - // html += `\n`; - // } - html += ` \n`; - html += ` \n`; - html += `
${componentHTML}
\n`; html += ` \n`; - html += ` \n`; + html += ` \n`; + if (isDevelopment()) { + html += `\n`; + } + + html += ` \n`; + html += ` \n`; + html += `
${componentHTML}
\n`; html += ` \n`; html += `\n`; diff --git a/src/functions/server/web-pages/grab-page-component.tsx b/src/functions/server/web-pages/grab-page-component.tsx index 46a4aa3..3a7b028 100644 --- a/src/functions/server/web-pages/grab-page-component.tsx +++ b/src/functions/server/web-pages/grab-page-component.tsx @@ -1,6 +1,5 @@ import type { FC } from "react"; import grabDirNames from "../../../utils/grab-dir-names"; -import grabPageName from "../../../utils/grab-page-name"; import grabRouteParams from "../../../utils/grab-route-params"; import grabRouter from "../../../utils/grab-router"; import type { BunextPageModule, GrabPageComponentRes } from "../../../types"; @@ -20,12 +19,7 @@ export default async function grabPageComponent({ const url = req?.url ? new URL(req.url) : undefined; const router = grabRouter(); - const { - BUNX_ROOT_500_PRESET_COMPONENT, - HYDRATION_DST_DIR, - BUNX_ROOT_500_FILE_NAME, - PAGES_DIR, - } = grabDirNames(); + const { BUNX_ROOT_500_PRESET_COMPONENT, PAGES_DIR } = grabDirNames(); const routeParams = req ? await grabRouteParams({ req }) : undefined; @@ -34,7 +28,7 @@ export default async function grabPageComponent({ if (!match?.filePath && url?.pathname) { const errMsg = `Page ${url.pathname} not found`; - console.error(errMsg); + // console.error(errMsg); throw new Error(errMsg); } @@ -42,11 +36,21 @@ export default async function grabPageComponent({ if (!file_path) { const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`; + // console.error(errMsg); + throw new Error(errMsg); + } + + const bundledMap = global.BUNDLER_CTX_MAP?.find( + (m) => m.local_path == file_path, + ); + + if (!bundledMap?.path) { + const errMsg = `No Bundled File Path for this request path!`; console.error(errMsg); throw new Error(errMsg); } - const pageName = grabPageName({ path: file_path }); + // const pageName = grabPageName({ path: file_path }); const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`; const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`; @@ -63,17 +67,19 @@ export default async function grabPageComponent({ ? root_pages_component_js_file : undefined; + const now = Date.now(); + const root_module = root_file - ? await import(`${root_file}?t=${global.LAST_BUILD_TIME ?? 0}`) + ? await import(`${root_file}?t=${now}`) : undefined; const RootComponent = root_module?.default as FC | undefined; - const component_file_path = root_module - ? `${file_path}` - : `${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`; + // const component_file_path = root_module + // ? `${file_path}` + // : `${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`; - const module: BunextPageModule = await import(component_file_path); + const module: BunextPageModule = await import(`${file_path}?t=${now}`); const serverRes = await (async () => { try { @@ -87,6 +93,7 @@ export default async function grabPageComponent({ })(); const Component = module.default as FC; + const component = RootComponent ? ( @@ -95,34 +102,30 @@ export default async function grabPageComponent({ ); - return { component, serverRes, routeParams, pageName, module }; + return { + component, + serverRes, + routeParams, + module, + bundledMap, + }; } catch (error: any) { + // console.log(`Grab page component ERROR =>`, error.message); + const match = router.match("/500"); const filePath = match?.filePath || BUNX_ROOT_500_PRESET_COMPONENT; - // if (!match?.filePath) { - // bundle({ - // out_dir: HYDRATION_DST_DIR, - // src: `${BUNX_ROOT_500_PRESET_COMPONENT}`, - // debug: true, - // }); - // } - const module: BunextPageModule = await import(filePath); - // const module: BunextPageModule = await import( - // `${filePath}?t=${global.LAST_BUILD_TIME ?? 0}` - // ); - const Component = module.default as FC; const component = ; return { component, - pageName: BUNX_ROOT_500_FILE_NAME, routeParams, module, + bundledMap: {}, }; } } diff --git a/src/functions/server/web-pages/grab-web-page-hydration-script.tsx b/src/functions/server/web-pages/grab-web-page-hydration-script.tsx new file mode 100644 index 0000000..e332cb2 --- /dev/null +++ b/src/functions/server/web-pages/grab-web-page-hydration-script.tsx @@ -0,0 +1,75 @@ +import grabDirNames from "../../../utils/grab-dir-names"; +import type { BundlerCTXMap, PageDistGenParams } from "../../../types"; +import grabConstants from "../../../utils/grab-constants"; + +const { BUNX_HYDRATION_SRC_DIR } = grabDirNames(); + +type Params = { + bundledMap: BundlerCTXMap; +}; + +export default async function ({ bundledMap }: Params) { + const { ClientRootElementIDName, ClientRootComponentWindowName } = + await grabConstants(); + + let script = ""; + + // script += `import React from "react";\n`; + // script += `import { hydrateRoot } from "react-dom/client";\n`; + // script += `import App from "${page_file}";\n`; + + // script += `declare global {\n`; + // script += ` interface Window {\n`; + // script += ` ${ClientWindowPagePropsName}: any;\n`; + // script += ` }\n`; + // script += `}\n`; + + // script += `let root: any = null;\n\n`; + // script += `const component = ;\n\n`; + // script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`; + // script += `if (container) {\n`; + // script += ` root = hydrateRoot(container, component);\n`; + // script += `}\n\n`; + script += `console.log(\`Development Environment\`);\n`; + // script += `console.log(import.meta);\n`; + + // script += `if (import.meta.hot) {\n`; + // script += ` console.log(\`HMR active\`);\n`; + // script += ` import.meta.hot.dispose(() => {\n`; + // script += ` console.log("dispose");\n`; + // script += ` });\n`; + // script += `}\n`; + + script += `const hmr = new EventSource("/__hmr");\n`; + script += `hmr.addEventListener("update", async (event) => {\n`; + // script += ` console.log(\`HMR even received:\`, event);\n`; + script += ` if (event.data) {\n`; + script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`; + // script += ` console.log("event", event);\n`; + // script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`; + // script += ` const event_data = JSON.parse(event.data);\n\n`; + // script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`; + + // script += ` console.log("event_data", event_data);\n\n`; + // script += ` console.log("new_js_path", new_js_path);\n\n`; + + // script += ` if (window.${ClientRootComponentWindowName}) {\n`; + // script += ` const new_component = await import(new_js_path);\n`; + // script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`; + // script += ` }\n`; + + // script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`; + // script += ` root.render(module.default);\n`; + // script += ` })\n`; + // script += ` console.log("root", root);\n`; + // script += ` root.unmount();\n`; + // script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`; + // script += ` root = hydrateRoot(container!, component);\n`; + // script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`; + // script += ` root.render(component);\n`; + script += ` window.location.reload();\n`; + script += ` }\n`; + script += ` });\n`; + + return script; +} diff --git a/src/functions/server/web-pages/handle-web-pages.tsx b/src/functions/server/web-pages/handle-web-pages.tsx index 13bef52..706a3d4 100644 --- a/src/functions/server/web-pages/handle-web-pages.tsx +++ b/src/functions/server/web-pages/handle-web-pages.tsx @@ -8,23 +8,16 @@ type Params = { export default async function ({ req }: Params): Promise { try { - const { component, pageName, module, serverRes } = + const { component, bundledMap, module, serverRes } = await grabPageComponent({ req }); const html = await genWebHTML({ component, pageProps: serverRes, - pageName, + bundledMap, module, }); - // writeWebPageHydrationScript({ - // component, - // pageName, - // module, - // pageProps: serverRes, - // }); - const res_opts: ResponseInit = { headers: { "Content-Type": "text/html", @@ -44,6 +37,8 @@ export default async function ({ req }: Params): Promise { return res; } catch (error: any) { + console.log(`Handle web pages Error =>`, error.message); + return new Response(error.message || `Page Not Found`, { status: 404, }); diff --git a/src/functions/server/web-pages/write-web-page-hydration-script.tsx b/src/functions/server/web-pages/write-web-page-hydration-script.tsx deleted file mode 100644 index 8158f1c..0000000 --- a/src/functions/server/web-pages/write-web-page-hydration-script.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { writeFileSync } from "fs"; -import path from "path"; -import grabDirNames from "../../../utils/grab-dir-names"; -import grabContants from "../../../utils/grab-constants"; -import type { PageDistGenParams } from "../../../types"; -import isDevelopment from "../../../utils/is-development"; - -const { BUNX_HYDRATION_SRC_DIR } = grabDirNames(); - -export default async function (params: PageDistGenParams) { - const { pageName, page_file } = params; - const { ClientRootElementIDName, ClientWindowPagePropsName } = - await grabContants(); - - const pageSrcTsFileName = `${pageName}.tsx`; - - let script = ""; - - script += `import React from "react";\n`; - script += `import { hydrateRoot } from "react-dom/client";\n`; - script += `import App from "${page_file}";\n`; - - script += `declare global {\n`; - script += ` interface Window {\n`; - script += ` ${ClientWindowPagePropsName}: any;\n`; - script += ` }\n`; - script += `}\n`; - - script += `let root: any = null;\n\n`; - script += `const component = ;\n\n`; - script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`; - script += `if (container) {\n`; - script += ` root = hydrateRoot(container, component);\n`; - script += `}\n\n`; - if (isDevelopment()) { - script += `const hmr = new EventSource("/__hmr");\n`; - script += `hmr.addEventListener("update", (event) => {\n`; - // script += ` console.log(\`HMR even received:\`, event);\n`; - script += ` if (event.data && root) {\n`; - script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`; - // script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`; - // script += ` root.render(module.default);\n`; - // script += ` })\n`; - // script += ` console.log("root", root);\n`; - // script += ` root.unmount();\n`; - // script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`; - // script += ` root = hydrateRoot(container!, component);\n`; - // script += ` root.render(component);\n`; - script += ` window.location.reload();\n`; - script += ` }\n`; - script += ` });\n`; - } - - const SRC_WRITE_FILE = path.join(BUNX_HYDRATION_SRC_DIR, pageSrcTsFileName); - writeFileSync(SRC_WRITE_FILE, script, "utf-8"); -} diff --git a/src/types/index.ts b/src/types/index.ts index 0d64ce8..3d3b85e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -128,7 +128,7 @@ export type LivePageDistGenParams = { head?: ReactNode; pageProps?: any; module?: BunextPageModule; - pageName: string; + bundledMap: BundlerCTXMap; }; export type BunextPageModule = { @@ -155,7 +155,7 @@ export type GrabPageComponentRes = { component: JSX.Element; serverRes?: BunextPageModuleServerReturn; routeParams?: BunxRouteParams; - pageName: string; + bundledMap: BundlerCTXMap; module: BunextPageModule; }; @@ -164,3 +164,20 @@ export type PageFiles = { url_path: string; file_name: string; }; + +export type BundlerCTXMap = { + path: string; + hash: string; + type: string; + entrypoint: string; + local_path: string; + url_path: string; + file_name: string; + css_path?: string; +}; + +export type GlobalHMRControllerObject = { + controller: ReadableStreamDefaultController; + page_url: string; + target_map: BundlerCTXMap; +}; diff --git a/src/utils/bundle.ts b/src/utils/bundle.ts index 53d4670..3b7ebed 100644 --- a/src/utils/bundle.ts +++ b/src/utils/bundle.ts @@ -1,12 +1,28 @@ import plugin from "bun-plugin-tailwind"; import { execSync, type ExecSyncOptions } from "child_process"; +const BuildKeys = [ + { key: "production" }, + { key: "bytecode" }, + { key: "conditions" }, + { key: "format" }, + { key: "root" }, + { key: "splitting" }, + { key: "cdd-chunking" }, +] as const; + type Params = { src: string; out_dir: string; + entry_naming?: string; minify?: boolean; exec_options?: ExecSyncOptions; debug?: boolean; + sourcemap?: boolean; + target?: "browser" | "node" | "bun"; + build_options?: { + [k in (typeof BuildKeys)[number]["key"]]: string | boolean; + }; }; export default function bundle({ @@ -15,15 +31,46 @@ export default function bundle({ minify = true, exec_options, debug, + entry_naming, + sourcemap, + target, + build_options, }: Params) { let cmd = `bun build`; - cmd += ` ${src} --outdir ${out_dir}`; - if (minify) { cmd += ` --minify`; } + if (entry_naming) { + cmd += ` --entry-naming "${entry_naming}"`; + } + + if (sourcemap) { + cmd += ` --sourcemap`; + } + + if (target) { + cmd += ` --target ${target}`; + } + + if (build_options) { + const keys = Object.keys(build_options); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] as (typeof BuildKeys)[number]["key"]; + const value = build_options[key]; + + if (typeof value == "boolean" && value) { + cmd += ` --${key}`; + } else if (key && value) { + cmd += ` --${key} ${value}`; + } + } + } + + cmd += ` ${src} --outdir ${out_dir}`; + if (debug) { console.log("cmd =>", cmd); } diff --git a/src/utils/grab-all-pages.ts b/src/utils/grab-all-pages.ts index fd1e067..2b5deae 100644 --- a/src/utils/grab-all-pages.ts +++ b/src/utils/grab-all-pages.ts @@ -2,6 +2,7 @@ import { existsSync, readdirSync, statSync } from "fs"; import grabDirNames from "./grab-dir-names"; import path from "path"; import type { PageFiles } from "../types"; +import AppNames from "./grab-app-names"; type Params = { exclude_api?: boolean; @@ -37,11 +38,11 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) { continue; } - if (page.match(/__root\.(tx|js)x?/)) { + if (page.match(new RegExp(`${AppNames["RootPagesComponentName"]}`))) { continue; } - if (page.match(/\(|\)/)) { + if (page.match(/\(|\)|--/)) { continue; } diff --git a/src/utils/grab-constants.ts b/src/utils/grab-constants.ts index 44ba275..b020846 100644 --- a/src/utils/grab-constants.ts +++ b/src/utils/grab-constants.ts @@ -7,6 +7,7 @@ export default async function grabConstants() { const ClientWindowPagePropsName = "__PAGE_PROPS__"; const ClientRootElementIDName = "__bunext"; + const ClientRootComponentWindowName = "BUNEXT_ROOT"; const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10; @@ -15,5 +16,6 @@ export default async function grabConstants() { ClientWindowPagePropsName, MBInBytes: MB_IN_BYTES, ServerDefaultRequestBodyLimitBytes, - }; + ClientRootComponentWindowName, + } as const; } diff --git a/src/utils/grab-dir-names.ts b/src/utils/grab-dir-names.ts index a7d67c7..4fd9b9c 100644 --- a/src/utils/grab-dir-names.ts +++ b/src/utils/grab-dir-names.ts @@ -7,6 +7,10 @@ export default function grabDirNames() { const API_DIR = path.join(PAGES_DIR, "api"); const PUBLIC_DIR = path.join(ROOT_DIR, "public"); const HYDRATION_DST_DIR = path.join(PUBLIC_DIR, "pages"); + const HYDRATION_DST_DIR_MAP_JSON_FILE = path.join( + HYDRATION_DST_DIR, + "map.json", + ); const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts"); const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext"); @@ -41,5 +45,6 @@ export default function grabDirNames() { BUNX_ROOT_PRESETS_DIR, BUNX_ROOT_500_PRESET_COMPONENT, BUNX_ROOT_500_FILE_NAME, + HYDRATION_DST_DIR_MAP_JSON_FILE, }; }