| ... | @@ -18,77 +18,92 @@ function loadMarkoExtra( | ... | @@ -18,77 +18,92 @@ function loadMarkoExtra( |
| 18 | mode: "html" | "markdown", | 18 | mode: "html" | "markdown", |
| 19 | ) { | 19 | ) { |
| 20 | let cache = markoCache.get(filepath); | 20 | let cache = markoCache.get(filepath); |
| 21 | if (!cache) { | 21 | if (!cache) markoCache.set(filepath, cache = buildMarkoCacheEntry(filepath, mode)); |
| 22 | let src = fs.readFileSync(filepath, "utf8"); | | |
| 23 | if (mode === "markdown") { | | |
| 24 | let transform; | | |
| 25 | try { | | |
| 26 | transform = markodown.transform({ | | |
| 27 | source: src, | | |
| 28 | selfImport: `./${path.basename(filepath)}`, | | |
| 29 | }); | | |
| 30 | } catch (err) { | | |
| 31 | throw new Error(`Failed ${filepath}: Markodown Compiler Panic`, { | | |
| 32 | cause: err, | | |
| 33 | }); | | |
| 34 | } | | |
| 35 | if (!transform.success) { | | |
| 36 | // TODO: better error handling | | |
| 37 | throw new Error( | | |
| 38 | "Failed " + filepath + ": " + JSON.stringify(transform.errors), | | |
| 39 | ); | | |
| 40 | } | | |
| 41 | src = transform.text; | | |
| 42 | | 22 | |
| 43 | fs.writeMkdirSync( | 23 | const { src, scannedClientRefs } = cache; |
| 44 | ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") | 24 | return hot.loadEsbuildCode(module, filepath, src, { |
| 45 | + ".marko", | 25 | scannedClientRefs, |
| 46 | src, | 26 | }); |
| | 27 | } |
| | 28 | |
| | 29 | export function buildMarkoCacheEntry( |
| | 30 | filepath: string, |
| | 31 | mode: "html" | "markdown" = "html", |
| | 32 | ): MarkoCacheEntry { |
| | 33 | let src = fs.readFileSync(filepath, "utf8"); |
| | 34 | if (mode === "markdown") { |
| | 35 | let transform; |
| | 36 | try { |
| | 37 | transform = markodown.transform({ |
| | 38 | source: src, |
| | 39 | selfImport: `./${path.basename(filepath)}`, |
| | 40 | }); |
| | 41 | } catch (err) { |
| | 42 | throw new Error(`Failed ${filepath}: Markodown Compiler Panic`, { |
| | 43 | cause: err, |
| | 44 | }); |
| | 45 | } |
| | 46 | if (!transform.success) { |
| | 47 | // TODO: better error handling |
| | 48 | throw new Error( |
| | 49 | "Failed " + filepath + ": " + JSON.stringify(transform.errors), |
| 47 | ); | 50 | ); |
| 48 | } | 51 | } |
| | 52 | src = transform.text; |
| | 53 | fs.writeMkdirSync(getMarkoCachePath(filepath, ".marko"), src); |
| | 54 | } |
| 49 | | 55 | |
| 50 | // A non-standard thing here is Clover Sitegen implements | 56 | const resolved = resolveClientImports(src, filepath); |
| 51 | // its own client side scripting stuff, so it overrides | 57 | let compiled = marko.compileSync(resolved.src, filepath, { |
| 52 | // bare client import statements to it's own usage. | 58 | sourceMaps: true, |
| 53 | const scannedClientRefs = new Set<string>(); | 59 | }); |
| 54 | if (src.match(/^\s*client\s+import\s+["']/m)) { | 60 | // workaround an esbuild bug? |
| 55 | src = src.replace( | 61 | (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/"; |
| 56 | /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]+/m, | 62 | compiled.map.sources[0] = filepath; |
| 57 | (_, src) => { | 63 | const compiledSrc = compiled.code |
| 58 | const ref = JSON.parse(`"${src.slice(1, -1)}"`); | 64 | + "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," |
| 59 | const resolved = hot.resolveClientRef(filepath, ref); | 65 | + Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64"); |
| 60 | scannedClientRefs.add(resolved); | 66 | fs.writeMkdirSync(getMarkoCachePath(filepath, ".js"), compiledSrc); |
| 61 | return `<CloverScriptInclude=${ | 67 | return { |
| 62 | JSON.stringify( | 68 | src: compiledSrc, |
| 63 | hot.getScriptId(resolved), | 69 | scannedClientRefs: resolved.scannedClientRefs, |
| 64 | ) | 70 | }; |
| 65 | } />`; | 71 | } |
| 66 | }, | 72 | |
| 67 | ) + "\nimport { CloverScriptInclude } from \"#sitegen\";\n"; | 73 | export function getMarkoCachePath( |
| 68 | } | 74 | filepath: string, |
| | 75 | ext: ".js" | ".marko", |
| | 76 | ) { |
| | 77 | return ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") |
| | 78 | + ext; |
| | 79 | } |
| 69 | | 80 | |
| 70 | let compiled = marko.compileSync(src, filepath, { | 81 | export function scanMarkoClientImports(src: string, filepath: string) { |
| 71 | sourceMaps: true, | 82 | // Clover Sitegen uses `client import` as a convenience shorthand |
| 72 | }); | 83 | // and rewrites it to the runtime script include component. |
| 73 | // workaround an esbuild bug? | 84 | const scannedClientRefs = new Set<string>(); |
| 74 | (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/"; | 85 | let found = false; |
| 75 | compiled.map.sources[0] = filepath; | 86 | src = src.replace( |
| 76 | src = compiled.code | 87 | /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]*$/gm, |
| 77 | + "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," | 88 | (_, rawRef) => { |
| 78 | + Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64"); | 89 | found = true; |
| 79 | cache = { src, scannedClientRefs: Array.from(scannedClientRefs) }; | 90 | const ref = JSON.parse(`"${rawRef.slice(1, -1)}"`); |
| 80 | markoCache.set(filepath, cache); | 91 | const resolved = hot.resolveClientRef(filepath, ref); |
| 81 | fs.writeMkdirSync( | 92 | scannedClientRefs.add(resolved); |
| 82 | ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") | 93 | return `<CloverScriptInclude=${JSON.stringify(hot.getScriptId(resolved))} />`; |
| 83 | + ".js", | 94 | }, |
| 84 | src, | 95 | ); |
| 85 | ); | 96 | if (found) { |
| | 97 | src += "\nimport { CloverScriptInclude } from \"#sitegen\";\n"; |
| 86 | } | 98 | } |
| | 99 | return { |
| | 100 | src, |
| | 101 | scannedClientRefs: Array.from(scannedClientRefs), |
| | 102 | }; |
| | 103 | } |
| 87 | | 104 | |
| 88 | const { src, scannedClientRefs } = cache; | 105 | function resolveClientImports(src: string, filepath: string) { |
| 89 | return hot.loadEsbuildCode(module, filepath, src, { | 106 | return scanMarkoClientImports(src, filepath); |
| 90 | scannedClientRefs, | | |
| 91 | }); | | |
| 92 | } | 107 | } |
| 93 | | 108 | |
| 94 | import * as fs from "#sitegen/fs"; | 109 | import * as fs from "#sitegen/fs"; |