Skip to content

Commit

Permalink
⚙️ kdjs: Support typed @define's
Browse files Browse the repository at this point in the history
 - We introduce a `define` pass to suport typed @define's in the
   preprocessing step.

   Unlike GCC defines, these defines are globally scoped and are
   easier to work with.

  Now one can do:
  ```javascript
  import { ChainId } from "/crosschain/chains";
  /** @define {ChainId} */
  const Chain = ChainId.x1;
  ```

  ```javascript
  import { ChainId } from "/crosschain/chains";
  /** @define {!Array<ChainId>} */
  const SupportedChains = [ChainId.x1, ChainId.xa4b1];
  ```
  • Loading branch information
KimlikDAO-bot committed Sep 12, 2024
1 parent 74c8686 commit d6c30df
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 43 deletions.
6 changes: 6 additions & 0 deletions kastro/compiler/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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";
Expand Down Expand Up @@ -94,8 +95,13 @@ const generateImage = (attribs, options) => {
const compilePage = async (componentName, pageGlobals) => {
pageGlobals.SharedCss = new Set();
pageGlobals.PageCss = new Set();
pageGlobals.GEN = false;
// TODO(KimlikDAO-bot): Remove when we have 3 languages
pageGlobals.TR = pageGlobals.Lang == LangCode.TR;
initGlobals(pageGlobals);

console.log(pageGlobals);

return compileComponent(componentName, {}, pageGlobals)
.then((html) => {
html = "<!DOCTYPE html>" + html;
Expand Down
10 changes: 1 addition & 9 deletions kastro/compiler/targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
hashAndCompressContent,
hashAndCompressFile
} from "../hashcache/compression";
import { define } from "./defines";

/**
* @param {!Object<string, *>} props
Expand All @@ -22,14 +21,7 @@ const compileScript = (props, globals) => {
entry,
output,
loose: "data-loose" in props ? true : false,
define: [
define("lib/util/dom", "GEN", false),
define("lib/util/dom", "TR", globals.Lang == "tr" ? "true" : "false"),
define("birim/dil/birim", "KonumTR", globals.RouteTR),
define("birim/dil/birim", "KonumEN", globals.RouteEN),
define("birim/cüzdan/birim", "Chains", globals.Chains.map((c) => c.id)).join("|"),
define("birim/cüzdan/birim", "DefaultChain", globals.DefaultChain)
]
globals
}).then(() => hashAndCompressFile(output))
.then((compressedName) => `<script src=${compressedName} type="module">`)
}
Expand Down
1 change: 0 additions & 1 deletion kastro/compiler/test/page.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ describe("compilePage() tests", () => {
SharedCss: new Set(),
PageCss: new Set()
});
console.log(html);
expect(html).toContain(`html lang="tr"`);
expect(html).not.toContain("l400.woff2");
expect(html).not.toContain("l700.woff2");
Expand Down
17 changes: 2 additions & 15 deletions kastro/crate.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import yaml from "js-yaml";
import { readFile, readdir } from "node:fs/promises";

/** @enum {string} */
const LangCode = {
EN: "en",
TR: "tr",
BG: "bg",
KZ: "kz",
};

/**
* @typedef {!Object<LangCode, string>}
*/
const PageRoute = {};
import { I18nString, LangCode } from "../util/i18n";

/**
* @typedef {{
* index: string,
* codebaseLang: LangCode,
* pages: !Array<PageRoute>
* pages: !Array<I18nString>
* }}
*/
const CrateRecipe = {};
Expand All @@ -40,6 +28,5 @@ const readCrateRecipe = (crateName) => readdir(crateName)
export {
CrateRecipe,
LangCode,
PageRoute,
readCrateRecipe
};
17 changes: 10 additions & 7 deletions kastro/workers/devServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ const readCrate = (crateName, buildMode) => readCrateRecipe(crateName)
const langs = crate.pages ? Object.keys(crate.pages[0]) : crate.languages;
const map = {};
const add = (page, rootComponent) => {
const routes = {};
for (const routeLang of langs)
routes[`Route${routeLang.toUpperCase()}`] = page[routeLang];
const route = {};
for (const lang of langs) {
const pageData = {
RootComponent: (crateName === "." ? "" : `${crateName}/`) + (rootComponent || page[crate.codebaseLang]),
BuildMode: buildMode,
Lang: lang,
CodebaseLang: crate.codebaseLang,
Route: { ...page }
};
map[`/${page[lang]}`] = Object.assign(pageData, routes);
delete pageData.Route[lang];
map[`/${page[lang]}`] = pageData;
}
};
add(Object.fromEntries(langs.map(lang => [lang, lang])), crate.index);
Expand Down Expand Up @@ -64,14 +64,17 @@ const serveCrate = async (crateName, buildMode) => {
},

transform(code, id) {
if (id.endsWith("cüzdan/birim.js"))
return code
.replace(/const Chains =.*?;/, `const Chains = ${JSON.stringify(currentPage.Chains)};`)
.replace(/const DefaultChain =.*?;/, `const DefaultChain = ${JSON.stringify(currentPage.DefaultChain)};`);
if (id.endsWith("dil/birim.js"))
return code
.replace(/const KonumTR =.*?;/, `const KonumTR = "${currentPage.RouteTR}"`)
.replace(/const KonumEN =.*?;/, `const KonumEN = "${currentPage.RouteEN}"`);
.replace(/const Route =.*?;/, `const Route = ${JSON.stringify(currentPage.Route)};`);
if (id.endsWith("util/dom.js"))
return code
.replace(/const GEN =.*?;/, `const GEN = false`)
.replace(/const TR =.*?;/, `const TR = ${currentPage.Lang == "tr" ? "true" : "false"}`);
.replace(/const TR =.*?;/, `const TR = ${currentPage.Lang == "tr" ? "true" : "false"};`);
}
}]
}).then((vite) => vite.listen(8787))
Expand Down
7 changes: 4 additions & 3 deletions kdjs/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ImportStatement } from "./modules";
import { postprocess } from "./postprocess";
import { preprocessAndIsolate } from "./preprocess";

/** @typedef {!Object<string, (string|boolean)>} */
/** @typedef {!Object<string, *>} */
const Params = {};

/**
Expand All @@ -26,7 +26,8 @@ const compile = async (params, checkFreshFn) => {
} = await preprocessAndIsolate(
/** @type {string} */(params["entry"]),
isolateDir,
[].concat(params["externs"] || [])
[].concat(params["externs"] || []),
/** @type {!Object<string, *>} */(params["globals"] || {})
);
/** @const {!Array<string>} */
const allFilesArray = Array.from(allFiles).sort();
Expand Down Expand Up @@ -64,7 +65,7 @@ const compile = async (params, checkFreshFn) => {
"entry_point": /** @type {string} */(params["entry"]),
};
if (params["define"])
options["define"] = params["define"];
options["define"] = /** @type {(!Array<string>|boolean|string)} */(params["define"]);

const closureCompiler = new ClosureCompiler.compiler(options);
closureCompiler.spawnOptions = {
Expand Down
32 changes: 31 additions & 1 deletion kdjs/externs/acorn.d.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const acorn = {};
/**
* @typedef {{
* ecmaVersion: string,
* sourceType: string
* sourceType: string,
* onComment: (!Array<!acorn.Comment>|undefined)
* }}
*/
acorn.ParseOptions;
Expand Down Expand Up @@ -247,6 +248,35 @@ acorn.ExportNamedDeclaration.prototype.declaration;
/** @const {!Array<!acorn.ExportSpecifier>} */
acorn.ExportNamedDeclaration.prototype.specifiers;

/**
* @typedef {{
* line: number,
* column: number
* }}
*/
acorn.Position;

/**
* @typedef {{
* source: string,
* start: acorn.Position,
* end: acorn.Position
* }}
*/
acorn.SourceLocation;

/**
* @typedef {{
* type: string,
* value: string,
* start: number,
* end: number,
* loc: acorn.SourceLocation,
* range: (!Array<number>|undefined)
* }}
*/
acorn.Comment;

/**
* @param {string} content
* @param {acorn.ParseOptions} options
Expand Down
56 changes: 49 additions & 7 deletions kdjs/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ const exportStmtToExportMap = (exportStmt) => {
* @param {string} file name of the file
* @param {string} content contents of the file
* @param {!Array<string>} files the stack of files, which will be pushed new files
* @param {!Object<string, *>} globals
* @param {!Map<string, ImportStatement>} unlinkedImports
* @return {string} the content after preprocessing
*/
const processJs = (isEntry, file, content, files, unlinkedImports) => {
const processJs = (isEntry, file, content, files, globals, unlinkedImports) => {
/** @const {!Array<!acorn.Comment>} */
const comments = [];
/** @const {!acorn.Program} */
const ast = parse(content, {
ecmaVersion: "latest",
sourceType: "module",
onComment: comments
});
/**
* @const
Expand All @@ -59,6 +63,42 @@ const processJs = (isEntry, file, content, files, unlinkedImports) => {
named: []
};

/**
* @param {!acorn.Comment} comment
*/
const processComment = (comment) => {
const defineIdx = comment.value.indexOf("@define");
if (defineIdx == -1) return;
const constDeclIndex = content.indexOf("const", comment.end);
if (constDeclIndex == -1) return;
const equalIndex = content.indexOf("=", constDeclIndex);
if (equalIndex == -1) return;

const symbol = content.substring(constDeclIndex + 5, equalIndex).trim();
if (symbol in globals) {
const typeBeg = comment.value.indexOf("{", defineIdx) + 1;
const typeEnd = comment.value.indexOf("}", typeBeg);
const type = comment.value.slice(typeBeg, typeEnd).trim();
const semicolonIndex = content.indexOf(';', equalIndex);
const lineEndIndex = content.indexOf("\n", equalIndex);
const assignmentEnd = semicolonIndex !== -1
? Math.min(semicolonIndex + 1, lineEndIndex) : lineEndIndex;

updates.push({
beg: comment.start + defineIdx + 2,
end: comment.start + defineIdx + 9,
put: "@const"
}, {
beg: comment.end,
end: assignmentEnd,
put: `\nconst ${symbol} = /** @type {${type}} */(${JSON.stringify(globals[symbol])});`
});
}
}

for (const comment of comments)
processComment(comment);

simple(ast, /** @type {!acorn.SimpleVisitor} */({
ImportDeclaration(node) {
/** @const {string} */
Expand Down Expand Up @@ -173,9 +213,10 @@ const processJs = (isEntry, file, content, files, unlinkedImports) => {
* @param {string} file name of the file
* @param {string} content of the file
* @param {!Array<string>} files
* @param {!Object<string, *>} globals
* @return {string} file after preprocessing
*/
const processJsx = (isEntry, file, content, files) => {
const processJsx = (isEntry, file, content, files, globals) => {
/** @const {!Array<string>} */
const lines = content.split("\n");
/** @const {!Array<string>} */
Expand All @@ -201,16 +242,17 @@ const processJsx = (isEntry, file, content, files) => {
/**
* @param {string} entryFile
* @param {string} isolateDir
* @param {!Array<string>=} externs
* @param {!Array<string>} externs
* @param {!Object<string, *>} globals
* @return {!Promise<{
* unlinkedImports: !Map<string, ImportStatement>,
* allFiles: !Set<string>
* }>}
*/
const preprocessAndIsolate = async (entryFile, isolateDir, externs) => {
const preprocessAndIsolate = async (entryFile, isolateDir, externs, globals) => {
const unlinkedImports = new Map();
/** @const {!Array<string>} */
const files = [entryFile].concat(externs || []);
const files = [entryFile, ...externs];
/** @const {!Set<string>} */
const allFiles = new Set();
/** @const {!Array<!Promise<void>>} */
Expand All @@ -222,8 +264,8 @@ const preprocessAndIsolate = async (entryFile, isolateDir, externs) => {
/** @const {string} */
const content = await readFile(file, "utf8");
const newContent = file.endsWith(".jsx")
? processJsx(file == entryFile, file, content, files)
: processJs(file == entryFile, file, content, files, unlinkedImports);
? processJsx(file == entryFile, file, content, files, globals)
: processJs(file == entryFile, file, content, files, globals, unlinkedImports);
const outFile = combine(isolateDir, file);
writePromises.push(mkdir(getDir(outFile), { recursive: true })
.then(() => writeFile(outFile, newContent)));
Expand Down
1 change: 1 addition & 0 deletions kdjs/textual.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Update = {};
* @return {string}
*/
const update = (orig, updates) => {
updates.sort((a, b) => a.beg - b.beg);
/** @type {string} */
let updated = "";
/** @type {number} */
Expand Down
19 changes: 19 additions & 0 deletions util/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


/** @enum {string} */
const LangCode = {
EN: "en",
TR: "tr",
BG: "bg",
KZ: "kz",
};

/**
* @typedef {!Object<LangCode, string>}
*/
const I18nString = {};

export {
LangCode,
I18nString
};

0 comments on commit d6c30df

Please sign in to comment.