Created
August 15, 2023 20:20
-
-
Save julrich/bfcb26bd35393a2818ade906b0315aa5 to your computer and use it in GitHub Desktop.
11ty + kickstartDS (Plugin)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const esbuild = require("esbuild"); | |
const { sassPlugin } = require("esbuild-sass-plugin"); | |
const { nodeExternalsPlugin } = require("esbuild-node-externals"); | |
const { renderToStaticMarkup } = require("react-dom/server"); | |
const lightningcss = require("lightningcss"); | |
const browserslistToEsbuild = require("browserslist-to-esbuild"); | |
const browserslist = require("browserslist"); | |
const browsers = browserslist(); | |
const esbuildTargets = browserslistToEsbuild(browsers); | |
const lightningcssTargets = lightningcss.browserslistToTargets(browsers); | |
const kds_exports = ["core", "base", "blog", "form", "content"].reduce( | |
(prev, curr) => { | |
try { | |
const exp = Object.keys( | |
require(`@kickstartds/${curr}/lib/exports.json`) | |
).map((e) => `node_modules/@kickstartds/${curr}/lib/` + e); | |
return prev.concat(exp); | |
} catch (e) { | |
return prev; | |
} | |
}, | |
[] | |
); | |
function findClientScripts(name, clientScripts, inputs) { | |
if (inputs[name]?.imports.length) { | |
for (const i of inputs[name].imports) { | |
if ( | |
i.path.endsWith(".js") && | |
(i.path.endsWith(".client.js") || kds_exports.includes(i.path)) | |
) { | |
clientScripts.push(i.path); | |
} else { | |
findClientScripts(i.path, clientScripts, inputs); | |
} | |
} | |
} | |
} | |
async function createPageContext(inputPath) { | |
return esbuild.context({ | |
stdin: { | |
contents: ` | |
import * as Page from "${inputPath}"; | |
page = { | |
component: (data) => <Page.default {...data} />, | |
frontmatter: Page.frontmatter, | |
}; | |
`, | |
resolveDir: process.cwd(), | |
loader: "jsx", | |
}, | |
jsx: "automatic", | |
write: false, | |
bundle: true, | |
outdir: ".", | |
treeShaking: true, | |
loader: { ".svg": "dataurl" }, | |
plugins: [ | |
sassPlugin(), | |
nodeExternalsPlugin({ | |
allowList: [ | |
"@kickstartds/core", | |
"@kickstartds/base", | |
"@kickstartds/content", | |
"@kickstartds/form", | |
], | |
}), | |
], | |
metafile: true, | |
platform: "node", | |
define: { | |
"process.env.NODE_ENV": JSON.stringify("production"), | |
}, | |
}); | |
} | |
function bundlePage(result, inputPath) { | |
const page = new Function( | |
"require", | |
result.outputFiles[0].text + " return page;" | |
)(require); | |
const deps = Object.keys(result.metafile.inputs) | |
.filter( | |
(dep) => | |
dep !== "<stdin>" && | |
dep !== inputPath.slice(2) && | |
!dep.startsWith("node_modules/") | |
) | |
.map((dep) => "./" + dep); | |
const clientScripts = []; | |
findClientScripts(inputPath.slice(2), clientScripts, result.metafile.inputs); | |
const { code: css } = lightningcss.transform({ | |
code: Buffer.from(result.outputFiles[1]?.text || ""), | |
minify: true, | |
targets: lightningcssTargets, | |
}); | |
return { | |
component: page.component, | |
data: page.frontmatter, | |
css, | |
clientScripts, | |
deps, | |
}; | |
} | |
async function bundleClientScripts(clientScripts) { | |
const clientResult = await esbuild.build({ | |
stdin: { | |
contents: clientScripts | |
.map((scriptPath) => `import "./${scriptPath}";`) | |
.join(""), | |
resolveDir: process.cwd(), | |
loader: "js", | |
}, | |
write: false, | |
bundle: true, | |
outdir: ".", | |
minify: true, | |
treeShaking: true, | |
platform: "browser", | |
target: esbuildTargets, | |
define: { | |
"process.env.NODE_ENV": JSON.stringify("production"), | |
}, | |
}); | |
return clientResult.outputFiles[0].text; | |
} | |
/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */ | |
module.exports = function kdsPlugin(eleventyConfig, options = {}) { | |
const { ignore = [] } = options; | |
const shouldIgnore = (inputPath) => { | |
for (const ignorePath of ignore) { | |
if (inputPath.startsWith(ignorePath)) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
eleventyConfig.addTemplateFormats("jsx"); | |
eleventyConfig.addTemplateFormats("tsx"); | |
const pageMap = new Map(); | |
const pageContexts = new Map(); | |
eleventyConfig.on("eleventy.after", ({ runMode }) => { | |
if (runMode === "build") { | |
for (const [, ctx] of pageContexts) { | |
ctx.dispose(); | |
} | |
} | |
}); | |
eleventyConfig.addExtension(["jsx", "tsx"], { | |
read: false, | |
getData: true, | |
compileOptions: { | |
// TODO: enable cache when deps watching is fixed | |
// https://github.com/11ty/eleventy/issues/2999 | |
cache: false, | |
}, | |
async getInstanceFromInputPath(inputPath) { | |
if (shouldIgnore(inputPath)) { | |
return { data: { eleventyExcludeFromCollections: true } }; | |
} | |
if (!pageContexts.has(inputPath)) { | |
pageContexts.set(inputPath, await createPageContext(inputPath)); | |
} | |
const ctx = pageContexts.get(inputPath); | |
const result = await ctx.rebuild(); | |
const { data, ...page } = bundlePage(result, inputPath); | |
pageMap.set(inputPath, page); | |
return { data }; | |
}, | |
async compile(content, inputPath) { | |
if (shouldIgnore(inputPath)) return; | |
const { component, clientScripts, css, deps } = pageMap.get(inputPath); | |
this.addDependencies(inputPath, deps); | |
return async (data) => { | |
const html = renderToStaticMarkup(component(data)); | |
const js = await bundleClientScripts(clientScripts); | |
return `${ | |
css ? `<style>${css}</style>` : "" | |
} ${html} <script type="module">${js}</script>`; | |
}; | |
}, | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment