commit 827d1dfc04406fce80bdccf3a330355d2459a570 Author: Nulo Date: Fri Aug 4 15:33:29 2023 -0300 otro generador de sitios estáticos? en serio? diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/gen.js b/gen.js new file mode 100755 index 0000000..826b700 --- /dev/null +++ b/gen.js @@ -0,0 +1,75 @@ +import { lstat, mkdir, readdir } from "fs/promises"; +import { dirname, join, relative, resolve } from "path"; +import { cwd } from "process"; + +import js from "./transformers/js.js"; +import copy from "./transformers/copy.js"; + +const sitegenConfigPath = join(cwd(), "sitegen.config.js"); +/** @type {any} */ +let config = {}; +try { + await lstat(sitegenConfigPath); + config = await import(sitegenConfigPath); +} catch {} + +const inputDirPath = "./src"; +const outputDirPath = join(cwd(), "_site"); + +const transformers = { + ".s.js": js, + ".s.jsx": js, + ".png": copy, + ".jpg": copy, + ".jpeg": copy, + ".avif": copy, + ".webp": copy, + ".woff": copy, + ".woff2": copy, + ...(config?.transformers || {}), +}; + +let built = 0; + +/** + * @param {string} path + */ +async function buildFile(path) { + const transformerEnt = Object.entries(transformers).find(([ext]) => + path.endsWith(ext) + ); + if (!transformerEnt) return; + const outputPath = join(outputDirPath, relative(inputDirPath, path)); + await mkdir(dirname(outputPath), { recursive: true }); + const transformer = transformerEnt[1]; + try { + await transformer.build(path, outputPath); + } catch (error) { + console.error(`While building ${path}`); + throw error; + } + built++; +} + +/** + * @param {string} dir + * @returns {Promise} + */ +async function buildDir(dir) { + const children = await readdir(dir, { withFileTypes: true }); + await Promise.all( + children.map((child) => { + const path = join(dir, child.name); + if (resolve(path) === resolve(outputDirPath)) return; + if (resolve(path) === resolve("node_modules")) return; + if (resolve(path) === resolve(".git")) return; + if (child.isDirectory()) return buildDir(path); + else if (child.isFile()) return buildFile(path); + }) + ); +} + +console.time("build"); +await buildDir(inputDirPath); +console.timeEnd("build"); +console.info(`Built ${built} files into ${outputDirPath}.`); diff --git a/package.json b/package.json new file mode 100644 index 0000000..4ef85d9 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "sitegen", + "type": "module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "bin": { + "sitegen": "./gen.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "esbuild": "^0.18.17", + "nanoid": "^4.0.2", + "vhtml": "^2.2.0" + }, + "devDependencies": { + "@tsconfig/node18": "^18.2.0", + "@types/node": "^20.4.6", + "typescript": "^5.1.6" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..cfabbd7 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,281 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + esbuild: + specifier: ^0.18.17 + version: 0.18.17 + nanoid: + specifier: ^4.0.2 + version: 4.0.2 + vhtml: + specifier: ^2.2.0 + version: 2.2.0 + +devDependencies: + '@tsconfig/node18': + specifier: ^18.2.0 + version: 18.2.0 + '@types/node': + specifier: ^20.4.6 + version: 20.4.6 + typescript: + specifier: ^5.1.6 + version: 5.1.6 + +packages: + + /@esbuild/android-arm64@0.18.17: + resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm@0.18.17: + resolution: {integrity: sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-x64@0.18.17: + resolution: {integrity: sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-arm64@0.18.17: + resolution: {integrity: sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-x64@0.18.17: + resolution: {integrity: sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-arm64@0.18.17: + resolution: {integrity: sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-x64@0.18.17: + resolution: {integrity: sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm64@0.18.17: + resolution: {integrity: sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm@0.18.17: + resolution: {integrity: sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ia32@0.18.17: + resolution: {integrity: sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-loong64@0.18.17: + resolution: {integrity: sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-mips64el@0.18.17: + resolution: {integrity: sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ppc64@0.18.17: + resolution: {integrity: sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-riscv64@0.18.17: + resolution: {integrity: sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-s390x@0.18.17: + resolution: {integrity: sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-x64@0.18.17: + resolution: {integrity: sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/netbsd-x64@0.18.17: + resolution: {integrity: sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/openbsd-x64@0.18.17: + resolution: {integrity: sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/sunos-x64@0.18.17: + resolution: {integrity: sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-arm64@0.18.17: + resolution: {integrity: sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-ia32@0.18.17: + resolution: {integrity: sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-x64@0.18.17: + resolution: {integrity: sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@tsconfig/node18@18.2.0: + resolution: {integrity: sha512-yhxwIlFVSVcMym3O31HoMnRXpoenmpIxcj4Yoes2DUpe+xCJnA7ECQP1Vw889V0jTt/2nzvpLQ/UuMYCd3JPIg==} + dev: true + + /@types/node@20.4.6: + resolution: {integrity: sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==} + dev: true + + /esbuild@0.18.17: + resolution: {integrity: sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.17 + '@esbuild/android-arm64': 0.18.17 + '@esbuild/android-x64': 0.18.17 + '@esbuild/darwin-arm64': 0.18.17 + '@esbuild/darwin-x64': 0.18.17 + '@esbuild/freebsd-arm64': 0.18.17 + '@esbuild/freebsd-x64': 0.18.17 + '@esbuild/linux-arm': 0.18.17 + '@esbuild/linux-arm64': 0.18.17 + '@esbuild/linux-ia32': 0.18.17 + '@esbuild/linux-loong64': 0.18.17 + '@esbuild/linux-mips64el': 0.18.17 + '@esbuild/linux-ppc64': 0.18.17 + '@esbuild/linux-riscv64': 0.18.17 + '@esbuild/linux-s390x': 0.18.17 + '@esbuild/linux-x64': 0.18.17 + '@esbuild/netbsd-x64': 0.18.17 + '@esbuild/openbsd-x64': 0.18.17 + '@esbuild/sunos-x64': 0.18.17 + '@esbuild/win32-arm64': 0.18.17 + '@esbuild/win32-ia32': 0.18.17 + '@esbuild/win32-x64': 0.18.17 + dev: false + + /nanoid@4.0.2: + resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: false + + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /vhtml@2.2.0: + resolution: {integrity: sha512-TPXrXrxBOslRUVnlVkiAqhoXneiertIg86bdvzionrUYhEuiROvyPZNiiP6GIIJ2Q7oPNVyEtIx8gMAZZE9lCQ==} + dev: false diff --git a/transformers/copy.js b/transformers/copy.js new file mode 100644 index 0000000..e2afbb8 --- /dev/null +++ b/transformers/copy.js @@ -0,0 +1,7 @@ +import { copyFile, constants } from "fs/promises"; + +export default class CopyTransformer { + static async build(inputPath, outputPath) { + await copyFile(inputPath, outputPath, constants.COPYFILE_FICLONE); + } +} diff --git a/transformers/js.js b/transformers/js.js new file mode 100644 index 0000000..122789e --- /dev/null +++ b/transformers/js.js @@ -0,0 +1,83 @@ +import { nanoid } from "nanoid"; +import { build } from "esbuild"; +import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import { cwd } from "process"; +import { basename, dirname, join } from "path"; +import h from "vhtml"; + +globalThis.vhtml = h; + +export default class JsTransformer { + /** + * @param {string} inputPath + * @param {string} outputPath + */ + static async build(inputPath, outputPath) { + outputPath = join( + dirname(outputPath), + basename(outputPath, ".s.jsx") + ".html" + ); + const outfile = join(cwd(), `${nanoid()}.mjs`); + await mkdir(dirname(outfile), { recursive: true }); + try { + await build({ + entryPoints: [join(cwd(), inputPath)], + outfile, + format: "esm", + loader: { + ".json": "json", + }, + + bundle: true, + platform: "node", + plugins: [ + { + name: "svg-jsx", + setup(build) { + build.onLoad({ filter: /\.svg$/ }, async (args) => { + const f = await readFile(args.path, "utf-8"); + const js = ` + export default (props) => { + let s = \`${f}\`; + if (props.class) + s = s.replace(" + ); + }; + `; + return { contents: js, loader: "jsx" }; + }); + }, + }, + { + name: "relative-import-path", + setup(build) { + build.onResolve({ filter: /./ }, (args) => { + if (args.path.endsWith(".json") || args.path.endsWith(".svg")) + return { path: join(args.resolveDir, args.path) }; + else if (args.path.startsWith(".")) + return { + path: join(args.resolveDir, args.path), + external: true, + }; + else return { path: args.path, external: !!args.importer }; + }); + }, + }, + ], + + jsxFactory: "h", + banner: { js: `const h = globalThis.vhtml;` }, + }); + const file = await import(outfile); + const html = "" + (await file.render()); + await writeFile(outputPath, html); + } finally { + } + await rm(outfile); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a7eab2e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@tsconfig/node18/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true + } +}