Skip to content

Commit

Permalink
🪡 Refactor kastro compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
KimlikDAO-bot committed Sep 14, 2024
1 parent 971b9a7 commit efe3b29
Show file tree
Hide file tree
Showing 15 changed files with 130 additions and 51 deletions.
44 changes: 32 additions & 12 deletions kastro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,52 @@ has many additional optimization passes and a full support for es6 modules.

There are 3 types of components:

1. **Singleton**: Only one instance can be present in the page. These bind to the DOM when
you import them and can keep an arbitrary internal state. (every variable that you
define in the module)
1. **Singleton**: Only one instance can be present in the page. These bind to
the DOM when you import them and can keep an arbitrary internal state (every
variable that you define in the module).
```javascript
const State = { 1n, 2n, 3n };
const SingletonComp = () => <div>Singleton</div>;
const State = [1, 2, 3];
@kastroSingletonComponent
const SingletonComp = (props) => <div>Singleton</div>;
export default SingletonComp;
```
If your component is exposing additional methods, you can instead use
```javascript
const State = [1, 2, 3];
/** @kastroSingletonComponent */
const SingletonComp = {
render: (props) => <div>Singleton</div>,
push: (x) => State.push(x),
pop: () => State.pop(),
}
export default SingletonComp;
```

2. **Stateless**: These are components that do not have any internal state besides the
DOM state.
2. **Stateless**: These are components that do not have any internal state
besides the DOM state. Since they are stateless, there can be arbitrary
number of instances in the page without any js objects being created. Kastro
compiler will generate the `bind()` invocations from the `render()` jsx
expression of the parent component.
```javascript
/**@kastroStatelessComponent */
const StatelessComp = {
render: ({id}) => <div id={id}>Stateless</div>,
bind: (id) => { document.getElementById(id).onclick = () => alert("hi"); }
render: ({ id }) => <div id={id}>on</div>,
bind: (id) => {
const root = document.getElementById(id);
root.onclick = () => root.innerText = root.innerText == "on" ? "off" : "on"
}
}
export default StatelessComp;
```

3. **Stateful**: These are components that have internal state and for each instance
of the component, a class instance is crated and exported.
```javascript
/** @kastroStatefulComponent */
class CheckBox {
static render({id}) { return <div id={id}>Stateful</div>; }
constructor(id) { this.root = document.getElementById(id); }
flip() {}
static render({ id }) { return <div id={id}>on</div>; }
constructor(id) { this.root = document.getElementById(id); this.on = true; }
flip() { this.on = !this.on; this.root.innerText = this.on ? "on" : "off"; }
}
export default CheckBox;
```
1 change: 1 addition & 0 deletions kastro/compiler/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ const compileComponent = (name, props, globals) => {
: import(process.cwd() + "/" + markup).then((mod) => mod.default(contextVars)))
.then((markupContent) => {
parser.end(markupContent);
if (css && !css.startsWith("/")) css = "/" + css;
if (css && !globals.SharedCss.has(css)
&& !globals.PageCss.has(css))
globals.PageCss.add(css);
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { spawn } from "bun";
import { cp, readFile, rename, writeFile } from "node:fs/promises";
import { keccak256Uint8 } from "../../crypto/sha3";
import { getExt } from "../../util/paths";
import { base64 } from "../../util/çevir";
import { keccak256Uint8 } from "../../../crypto/sha3";
import { getExt } from "../../../util/paths";
import { base64 } from "../../../util/çevir";

const zopfli = (inputName, outputName) => spawn({
cmd: [
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
62 changes: 59 additions & 3 deletions kastro/compiler/image.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,64 @@
import { readFile } from "node:fs/promises";
import { SAXParser } from "sax";
import { optimize } from "svgo";
import { tagYaz } from "../../util/html";
import { getByKey } from "./hashcache/buildCache";
import SvgoInlineConfig from "./svgoInlineConfig";

const removeGlobalProps = (props) => {
for (const prop in props)
if (prop.charCodeAt(0) < 91)
delete props[prop];
}

const Image = ({ src, inline }) => {
console.log(src, inline);
return <img src={src} data-inline={inline} />;
/**
* We optimize the inline svgs regardless of the build mode.
*
* @param {!Object<string, *>} props
* @returns {!Promise<string>}
*/
const compileInlineSvg = ({ src, ...props }) =>
getByKey("build/" + src, () =>
getByKey(src, () => readFile(src, "utf-8"))
.then((svg) => optimize(svg, SvgoInlineConfig).data))
.then((svg) => {
removeGlobalProps(props);
delete props.inline;
const parser = new SAXParser(true);
let result = "";
parser.onopentag = ({ name, attributes }) => {
if (name === "svg")
Object.assign(attributes, props);
result += tagYaz(name, attributes, false);
};
parser.ontext = (text) => {
result += text;
};
parser.onclosetag = (tagName) => {
result += `</${tagName}>`;
};
parser.write(svg).close();
return result;
});


/**
* @param {!Object<string, *>} props
* @return {!Promise<string>}
*/
const Image = (props) => {
const { inline, src, ...restProps } = props;
if (inline) {
if (!src.endsWith(".svg"))
throw new Error("We only inline svgs; for other formats serving directly is more efficient");
return compileInlineSvg(props)
}

for (const prop in props)
if (prop[0] === prop[0].toUpperCase())
delete props[prop];

return tagYaz("img", props, true);
};

export { Image };
43 changes: 16 additions & 27 deletions kastro/compiler/page.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
import { plugin } from "bun";
import { minify } from "html-minifier";
import assert from "node:assert";
import { readFile } from "node:fs/promises";
import process from "node:process";
import { optimize } from "svgo";
import { tagYaz } from "../../util/html";
import { LangCode } from "../../util/i18n";
import { getExt } from "../../util/paths";
import { getByKey } from "../hashcache/buildCache";
import { hashAndCompressContent, hashFile } from "../hashcache/compression";
import { compileComponent } from "./component";
import { getByKey } from "./hashcache/buildCache";
import { hashAndCompressContent, hashFile } from "./hashcache/compression";
import HtmlMinifierConfig from "./htmlMinifierConfig";
import { initGlobals } from "./pageGlobals";
import SvgoConfig from "./svgoConfig";
import SvgoInlineConfig from "./svgoInlineConfig";
import { generateStylesheet, webp } from "./targets";

const setupEnvironment = () => {
const ImagePlugin = {
name: 'kastro image loader',
const KastroPlugin = {
name: 'kastro loader',
setup(build) {
const cwdLen = process.cwd().length;
build.onLoad({ filter: /\.svg$/ }, (args) => {
const cwdLen = process.cwd().length + 1;
build.onLoad({ filter: /\.(svg|png|webp)$/ }, (args) => {
const code = `import { Image } from "@kimlikdao/lib/kastro/compiler/image";\n` +
`export default (props) => Image({...props, src: "${args.path.slice(cwdLen)}" });`;
return {
contents: code,
loader: "js"
};
});
build.onLoad({ filter: /\.css$/ }, (args) => {
const code = `import { StyleSheet } from "@kimlikdao/lib/kastro/compiler/stylesheet";\n` +
`export default (props) => StyleSheet({...props, src: "${args.path.slice(cwdLen)}" });`;
return {
contents: code,
loader: "js"
};
});
},
};

plugin(ImagePlugin);
plugin(KastroPlugin);

globalThis.GEN = true;
globalThis.document = {};
Expand Down Expand Up @@ -102,7 +107,6 @@ const compilePage = async (componentName, pageGlobals) => {
return compileComponent(componentName, {}, pageGlobals)
.then((html) => {
html = "<!DOCTYPE html>" + html;
console.log(pageGlobals);
if (pageGlobals.BuildMode == 0) {
/** @type {string} */
let links = "";
Expand All @@ -120,23 +124,8 @@ const compilePage = async (componentName, pageGlobals) => {
});
}

/**
* Verilen konumdan svg içeriğini okur. Eğer uzantı .m.js ise içeriğini kasto
* kurallarına göre günceller.
*
* Eğer `!seçimler.dev` is içeriği svgo ile optimize eder.
*
* @param {!Object<string, string>} seçimler
* @return {!Promise<string>}
*/
const compileSvg = (seçimler) => (
seçimler.konum.endsWith(".m.svg")
? compileComponent(seçimler.konum, seçimler)
: readFile(seçimler.konum, "utf8"))
.then((svg) => seçimler.dev ? svg : optimize(svg, SvgoConfig).data);

export {
compilePage,
compileSvg
compilePage
};

7 changes: 7 additions & 0 deletions kastro/compiler/stylesheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

const StyleSheet = ({ src, shared, SharedCss, PageCss }) => {
(shared ? SharedCss : PageCss).add(src);
return;
}

export { StyleSheet };
3 changes: 1 addition & 2 deletions kastro/compiler/targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getDir } from "../../util/paths";
import {
hashAndCompressContent,
hashAndCompressFile
} from "../hashcache/compression";
} from "./hashcache/compression";

/**
* @param {!Object<string, *>} props
Expand Down Expand Up @@ -35,7 +35,6 @@ const generateStylesheet = (cssFileNames) =>
.then((csses) => hashAndCompressContent(minify(csses.join("\n")).css, "css"))
.then((hashedName) => `<link rel="stylesheet" src=${hashedName} type="text/css">`);


const webp = (inputName, outputName, passes = 10, quality = 70) =>
mkdir(getDir(outputName), { recursive: true }).then(() =>
spawn([
Expand Down
8 changes: 7 additions & 1 deletion kastro/workers/devServer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createServer } from "vite";
import { parseArgs } from "../../util/cli";
import { BuildMode } from "../compiler/compiler";
import { compilePage, compileSvg } from "../compiler/page";
import { compilePage } from "../compiler/page";
import { readCrateRecipe } from "../crate";

/**
Expand Down Expand Up @@ -74,6 +74,12 @@ const serveCrate = async (crateName, buildMode) => {
return code
.replace(/const GEN =.*?;/, `const GEN = false`)
.replace(/const TR =.*?;/, `const TR = ${currentPage.Lang == "tr" ? "true" : "false"};`);
if (id.endsWith(".jsx")) {
const lines = code.split("\n");
const filteredLines = lines.filter((line) => line.includes("util/dom") ||
line.trim().startsWith("export const"));
return filteredLines.join("\n");
}
}
}]
}).then((vite) => vite.listen(8787))
Expand Down
4 changes: 2 additions & 2 deletions kdjs/passes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ smart simplifications on a macroscoping scale to make the final code
more efficient and compact.
Don't worry about minification or variable renamings; those tedious tasks
will be handler later with simpler tools. Your task is to come up with some
will be handled later with simpler tools. Your task is to come up with some
statements about the code and prove them correct and make simplification
based on these facts.
Expand All @@ -30,7 +30,7 @@ remove it. Or may prove that some properties of an object is never read,
and remove them.
Or you may prove that some function is pure and constant-propogate through
the function (if the parameters are known at compiler time, the function
the function (if the parameters are known at compile time, the function
call can be replaced with the result of the function). Since you are given
a single module only, you may not always figure out that a function is pure.
However if the function name looks like it could be pure, (such as add(a, b),
Expand Down
2 changes: 1 addition & 1 deletion kdjs/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { existsSync } from "node:fs";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { combine, getDir } from "../util/paths";
import { ExportStatement, ImportStatement } from "./modules";
import { Update, update } from "./textual";
import { serializeWithStringKeys } from "./objects";
import { Update, update } from "./textual";

const PACKAGE_EXTERNS = "node_modules/@kimlikdao/kdjs/externs/";

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"html-minifier": "^4.0.0",
"htmlparser2": "^9.1.0",
"js-yaml": "^4.1.0",
"sax": "^1.4.1",
"svgo": "^3.3.2",
"uglify-js": "~3.19.3"
}
Expand Down

0 comments on commit efe3b29

Please sign in to comment.