diff --git a/kastro/README.md b/kastro/README.md
index d067242..89f2cd5 100644
--- a/kastro/README.md
+++ b/kastro/README.md
@@ -24,21 +24,40 @@ 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 = () =>
Singleton
;
+ const State = [1, 2, 3];
+ @kastroSingletonComponent
+ const SingletonComp = (props) => Singleton
;
+ 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) => Singleton
,
+ 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}) => Stateless
,
- bind: (id) => { document.getElementById(id).onclick = () => alert("hi"); }
+ render: ({ id }) => on
,
+ bind: (id) => {
+ const root = document.getElementById(id);
+ root.onclick = () => root.innerText = root.innerText == "on" ? "off" : "on"
+ }
}
export default StatelessComp;
```
@@ -46,10 +65,11 @@ There are 3 types of components:
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 Stateful
; }
- constructor(id) { this.root = document.getElementById(id); }
- flip() {}
+ static render({ id }) { return on
; }
+ 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;
```
diff --git a/kastro/compiler/component.js b/kastro/compiler/component.js
index c575709..15341d2 100644
--- a/kastro/compiler/component.js
+++ b/kastro/compiler/component.js
@@ -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);
diff --git a/kastro/hashcache/buildCache.js b/kastro/compiler/hashcache/buildCache.js
similarity index 100%
rename from kastro/hashcache/buildCache.js
rename to kastro/compiler/hashcache/buildCache.js
diff --git a/kastro/hashcache/compression.js b/kastro/compiler/hashcache/compression.js
similarity index 93%
rename from kastro/hashcache/compression.js
rename to kastro/compiler/hashcache/compression.js
index 494f131..a968a9e 100644
--- a/kastro/hashcache/compression.js
+++ b/kastro/compiler/hashcache/compression.js
@@ -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: [
diff --git a/kastro/hashcache/fileCache.js b/kastro/compiler/hashcache/fileCache.js
similarity index 100%
rename from kastro/hashcache/fileCache.js
rename to kastro/compiler/hashcache/fileCache.js
diff --git a/kastro/hashcache/files.js b/kastro/compiler/hashcache/files.js
similarity index 100%
rename from kastro/hashcache/files.js
rename to kastro/compiler/hashcache/files.js
diff --git a/kastro/hashcache/targets.js b/kastro/compiler/hashcache/targets.js
similarity index 100%
rename from kastro/hashcache/targets.js
rename to kastro/compiler/hashcache/targets.js
diff --git a/kastro/compiler/image.js b/kastro/compiler/image.js
index 784a824..34d17c1 100644
--- a/kastro/compiler/image.js
+++ b/kastro/compiler/image.js
@@ -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 ;
+/**
+ * We optimize the inline svgs regardless of the build mode.
+ *
+ * @param {!Object} props
+ * @returns {!Promise}
+ */
+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} props
+ * @return {!Promise}
+ */
+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 };
diff --git a/kastro/compiler/page.js b/kastro/compiler/page.js
index 90f70fa..03494ca 100644
--- a/kastro/compiler/page.js
+++ b/kastro/compiler/page.js
@@ -1,27 +1,24 @@
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 {
@@ -29,10 +26,18 @@ const setupEnvironment = () => {
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 = {};
@@ -102,7 +107,6 @@ const compilePage = async (componentName, pageGlobals) => {
return compileComponent(componentName, {}, pageGlobals)
.then((html) => {
html = "" + html;
- console.log(pageGlobals);
if (pageGlobals.BuildMode == 0) {
/** @type {string} */
let links = "";
@@ -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} seçimler
- * @return {!Promise}
- */
-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
};
diff --git a/kastro/compiler/stylesheet.js b/kastro/compiler/stylesheet.js
new file mode 100644
index 0000000..080d951
--- /dev/null
+++ b/kastro/compiler/stylesheet.js
@@ -0,0 +1,7 @@
+
+const StyleSheet = ({ src, shared, SharedCss, PageCss }) => {
+ (shared ? SharedCss : PageCss).add(src);
+ return;
+}
+
+export { StyleSheet };
diff --git a/kastro/compiler/targets.js b/kastro/compiler/targets.js
index d556f02..e6ad470 100644
--- a/kastro/compiler/targets.js
+++ b/kastro/compiler/targets.js
@@ -6,7 +6,7 @@ import { getDir } from "../../util/paths";
import {
hashAndCompressContent,
hashAndCompressFile
-} from "../hashcache/compression";
+} from "./hashcache/compression";
/**
* @param {!Object} props
@@ -35,7 +35,6 @@ const generateStylesheet = (cssFileNames) =>
.then((csses) => hashAndCompressContent(minify(csses.join("\n")).css, "css"))
.then((hashedName) => ` `);
-
const webp = (inputName, outputName, passes = 10, quality = 70) =>
mkdir(getDir(outputName), { recursive: true }).then(() =>
spawn([
diff --git a/kastro/workers/devServer.js b/kastro/workers/devServer.js
index e5c75b3..ce7d85a 100644
--- a/kastro/workers/devServer.js
+++ b/kastro/workers/devServer.js
@@ -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";
/**
@@ -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))
diff --git a/kdjs/passes.js b/kdjs/passes.js
index b60c260..954f999 100644
--- a/kdjs/passes.js
+++ b/kdjs/passes.js
@@ -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.
@@ -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),
diff --git a/kdjs/preprocess.js b/kdjs/preprocess.js
index 4211e48..a6aafcf 100644
--- a/kdjs/preprocess.js
+++ b/kdjs/preprocess.js
@@ -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/";
diff --git a/package.json b/package.json
index 9d81a88..b87c7ae 100644
--- a/package.json
+++ b/package.json
@@ -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"
}