| ... | ... | @@ -18,77 +18,92 @@ function loadMarkoExtra( |
| 18 | 18 | mode: "html" | "markdown", |
| 19 | 19 | ) { |
| 20 | 20 | let cache = markoCache.get(filepath); |
| 21 | | if (!cache) { |
| 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; |
| 21 | if (!cache) markoCache.set(filepath, cache = buildMarkoCacheEntry(filepath, mode)); |
| 42 | 22 | |
| 43 | | fs.writeMkdirSync( |
| 44 | | ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") |
| 45 | | + ".marko", |
| 46 | | src, |
| 23 | const { src, scannedClientRefs } = cache; |
| 24 | return hot.loadEsbuildCode(module, filepath, src, { |
| 25 | scannedClientRefs, |
| 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 |
| 51 | | // its own client side scripting stuff, so it overrides |
| 52 | | // bare client import statements to it's own usage. |
| 53 | | const scannedClientRefs = new Set<string>(); |
| 54 | | if (src.match(/^\s*client\s+import\s+["']/m)) { |
| 55 | | src = src.replace( |
| 56 | | /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]+/m, |
| 57 | | (_, src) => { |
| 58 | | const ref = JSON.parse(`"${src.slice(1, -1)}"`); |
| 59 | | const resolved = hot.resolveClientRef(filepath, ref); |
| 60 | | scannedClientRefs.add(resolved); |
| 61 | | return `<CloverScriptInclude=${ |
| 62 | | JSON.stringify( |
| 63 | | hot.getScriptId(resolved), |
| 64 | | ) |
| 65 | | } />`; |
| 66 | | }, |
| 67 | | ) + "\nimport { CloverScriptInclude } from \"#sitegen\";\n"; |
| 68 | | } |
| 56 | const resolved = resolveClientImports(src, filepath); |
| 57 | let compiled = marko.compileSync(resolved.src, filepath, { |
| 58 | sourceMaps: true, |
| 59 | }); |
| 60 | // workaround an esbuild bug? |
| 61 | (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/"; |
| 62 | compiled.map.sources[0] = filepath; |
| 63 | const compiledSrc = compiled.code |
| 64 | + "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," |
| 65 | + Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64"); |
| 66 | fs.writeMkdirSync(getMarkoCachePath(filepath, ".js"), compiledSrc); |
| 67 | return { |
| 68 | src: compiledSrc, |
| 69 | scannedClientRefs: resolved.scannedClientRefs, |
| 70 | }; |
| 71 | } |
| 72 | |
| 73 | export function getMarkoCachePath( |
| 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, { |
| 71 | | sourceMaps: true, |
| 72 | | }); |
| 73 | | // workaround an esbuild bug? |
| 74 | | (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/"; |
| 75 | | compiled.map.sources[0] = filepath; |
| 76 | | src = compiled.code |
| 77 | | + "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," |
| 78 | | + Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64"); |
| 79 | | cache = { src, scannedClientRefs: Array.from(scannedClientRefs) }; |
| 80 | | markoCache.set(filepath, cache); |
| 81 | | fs.writeMkdirSync( |
| 82 | | ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") |
| 83 | | + ".js", |
| 84 | | src, |
| 85 | | ); |
| 81 | export function scanMarkoClientImports(src: string, filepath: string) { |
| 82 | // Clover Sitegen uses `client import` as a convenience shorthand |
| 83 | // and rewrites it to the runtime script include component. |
| 84 | const scannedClientRefs = new Set<string>(); |
| 85 | let found = false; |
| 86 | src = src.replace( |
| 87 | /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]*$/gm, |
| 88 | (_, rawRef) => { |
| 89 | found = true; |
| 90 | const ref = JSON.parse(`"${rawRef.slice(1, -1)}"`); |
| 91 | const resolved = hot.resolveClientRef(filepath, ref); |
| 92 | scannedClientRefs.add(resolved); |
| 93 | return `<CloverScriptInclude=${JSON.stringify(hot.getScriptId(resolved))} />`; |
| 94 | }, |
| 95 | ); |
| 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; |
| 89 | | return hot.loadEsbuildCode(module, filepath, src, { |
| 90 | | scannedClientRefs, |
| 91 | | }); |
| 105 | function resolveClientImports(src: string, filepath: string) { |
| 106 | return scanMarkoClientImports(src, filepath); |
| 92 | 107 | } |
| 93 | 108 | |
| 94 | 109 | import * as fs from "#sitegen/fs"; |