UNPKG

5.47 kBJavaScriptView Raw
1import fs from "fs/promises"
2import path from "path"
3import vite from "vite"
4import { compile } from "xdm"
5
6const mdx = ({ rehypePlugins } = {}) => {
7 return {
8 name: "bruh-mdx",
9 enforce: "pre",
10
11 async transform(source, id) {
12 if (!id.endsWith(".mdx"))
13 return
14
15 const result = await compile(source, {
16 rehypePlugins,
17 jsxRuntime: "classic",
18 pragma: "h",
19 pragmaFrag: "JSXFragment"
20 })
21
22 const code = result.value
23 .replace(
24 `import h from "react"`,
25 `import { h, JSXFragment } from "bruh/dom"`
26 )
27 .replace(
28 /classname/igm,
29 "class"
30 )
31
32 return {
33 code,
34 map: { mappings: "" }
35 }
36 }
37 }
38}
39
40const excludeEntry = (entry, directory) =>
41 entry.isDirectory() && entry.name == "node_modules"
42
43const getHtmlRenderFiles = async (directory, htmlRenderFileExtention, maxDepth = Infinity) => {
44 if (maxDepth < 1)
45 return []
46
47 try {
48 const entries = await fs.readdir(directory, { withFileTypes: true })
49 const unflattenedFiles = await Promise.all(
50 entries
51 .map(async entry => {
52 if (excludeEntry(entry, directory))
53 return []
54
55 const entryPath = path.join(directory, entry.name)
56
57 if (entry.isDirectory())
58 return getHtmlRenderFiles(entryPath, htmlRenderFileExtention, maxDepth - 1)
59 if (htmlRenderFileExtention.test(entry.name))
60 return [entryPath]
61
62 return []
63 })
64 )
65 return unflattenedFiles.flat()
66 }
67 catch {
68 return []
69 }
70}
71
72export const bruhDev = ({ htmlRenderFileExtention, root, external } = {}) => {
73 let config = {}
74
75 const urlToHtmlRenderFile = async url => {
76 const resolvedRoot = root || path.resolve(config.root || "")
77 const pathname = path.join(resolvedRoot, path.normalize(url))
78 const htmlRenderFiles = await getHtmlRenderFiles(path.dirname(pathname), htmlRenderFileExtention, 2)
79 for (const htmlRenderFile of htmlRenderFiles) {
80 const htmlRenderFileName = htmlRenderFile.replace(htmlRenderFileExtention, "")
81 if (htmlRenderFileName == pathname)
82 return htmlRenderFile
83 if (htmlRenderFileName == path.join(pathname, "index"))
84 return htmlRenderFile
85 }
86 }
87
88 return {
89 name: "bruh-dev",
90 apply: "serve",
91 enforce: "pre",
92
93 config() {
94 return {
95 ssr: {
96 external
97 }
98 }
99 },
100
101 configResolved(resolvedConfig) {
102 config = resolvedConfig
103 },
104
105 configureServer(viteDevServer) {
106 viteDevServer.middlewares.use(async (req, res, next) => {
107 try {
108 const htmlRenderFile = await urlToHtmlRenderFile(req.url)
109 if (htmlRenderFile) {
110 const { default: render } = await viteDevServer.ssrLoadModule(htmlRenderFile)
111 const rendered = await render()
112 const transformedHTML = await viteDevServer.transformIndexHtml(req.url, rendered.toString())
113
114 res.setHeader("Content-Type", "text/html")
115 return res.end(transformedHTML)
116 }
117 next()
118 }
119 catch (error) {
120 viteDevServer.ssrFixStacktrace(error)
121 console.error(error)
122
123 res.statusCode = 500
124 return res.end(error.stack)
125 }
126 })
127 }
128 }
129}
130
131export const bruhBuild = ({ htmlRenderFileExtention, root } = {}) => {
132 let viteDevServer
133
134 const idToHtmlRenderFile = {}
135
136 return {
137 name: "bruh-build",
138 apply: "build",
139 enforce: "pre",
140
141 async buildStart() {
142 viteDevServer = await vite.createServer()
143 },
144
145 async resolveId(source) {
146 if (htmlRenderFileExtention.test(source)) {
147 const id = source.replace(htmlRenderFileExtention, ".html")
148 idToHtmlRenderFile[id] = source
149 return id
150 }
151 },
152
153 async load(id) {
154 if (!idToHtmlRenderFile[id])
155 return
156
157 const { default: render } = await viteDevServer.ssrLoadModule(idToHtmlRenderFile[id])
158 const rendered = await render()
159 return {
160 code: rendered,
161 map: ""
162 }
163 },
164
165 async closeBundle() {
166 return viteDevServer.close()
167 },
168
169 // Add all page render files to the build inputs
170 async config(config) {
171 const resolvedRoot = root || path.resolve(config.root || "")
172 const htmlRenderFiles = await getHtmlRenderFiles(resolvedRoot, htmlRenderFileExtention)
173
174 const input = Object.fromEntries(
175 htmlRenderFiles
176 .map(pathname => {
177 const name = path.relative(resolvedRoot, pathname).replace(htmlRenderFileExtention, "")
178 return [name, pathname]
179 })
180 )
181
182 return {
183 build: {
184 rollupOptions: {
185 input
186 }
187 }
188 }
189 }
190 }
191}
192
193export const bruhJSX = () => {
194 return {
195 name: "bruh-jsx",
196
197 config() {
198 return {
199 esbuild: {
200 jsxFactory: "h",
201 jsxFragment: "JSXFragment",
202 jsxInject: `import { h, JSXFragment } from "bruh/dom"`
203 }
204 }
205 }
206 }
207}
208
209export const bruh = ({
210 htmlRenderFileExtention = /\.html\.(mjs|jsx?|tsx?)$/,
211 root,
212 external = [],
213 rehypePlugins = []
214} = {}) =>
215 [
216 mdx({
217 rehypePlugins
218 }),
219 bruhDev({
220 htmlRenderFileExtention,
221 root,
222 external: ["fs", "path", "crypto", ...external]
223 }),
224 bruhBuild({
225 htmlRenderFileExtention,
226 root
227 }),
228 bruhJSX()
229 ]
230
231export default bruh