commit 5e70cff03b0b9cca82ba30c603bd3d32e2dfa132
parent 7b790c499dad708b29cca419fc751ddedc3f70c3
Author: tongong <tongong@gmx.net>
Date: Sat, 16 Jul 2022 11:30:00 +0200
finished javascript module bundling
Diffstat:
5 files changed, 93 insertions(+), 27 deletions(-)
diff --git a/bundle_js.ha b/bundle_js.ha
@@ -35,23 +35,22 @@ fn tacker_js(inputpath: str, ofile: io::handle, html: bool) void = {
let g: dep_graph = [];
defer dep_graph_free(g);
dep_add(void, inputpath, &g);
- for (let i = 0z; i < len(g); i += 1) {
- fmt::printf("{}: {} - ", i, g[i].path)!;
- const dep = g[i].dependencies;
- for (let j = 0z; j < len(dep); j += 1) {
- fmt::printf("{},", dep[j])!;
- };
- fmt::println("")!;
- };
const sorting = sort_kahn(g, inputpath);
defer free(sorting);
- fmt::println("sorting:")!;
+ fmt::fprintln(ofile, "(function() {")!;
for (let i = 0z; i < len(sorting); i += 1) {
- fmt::printfln("- {}", g[sorting[i]].path)!;
+ fmt::fprintfln(ofile, "const _tacker{} = (function() {{", sorting[i])!;
+ fmt::fprintln(ofile, "const module = { exports: {} }, exports = module.exports;")!;
+ emit_bundled(g[sorting[i]].path, ofile, g, html);
+ fmt::fprintln(ofile, "return module.exports;")!;
+ fmt::fprintln(ofile, "})();")!;
};
+ fmt::fprintln(ofile, "})();")!;
};
+
let p_req: searchio::pattern = searchio::pattern {...};
+let p_reqscript: searchio::pattern = searchio::pattern {...};
let p_newline: searchio::pattern = searchio::pattern {...};
let p_commentend: searchio::pattern = searchio::pattern {...};
let p_quotedouble: searchio::pattern = searchio::pattern {...};
@@ -60,6 +59,8 @@ let p_quotesingle: searchio::pattern = searchio::pattern {...};
@init fn init() void = {
// "/" has to be recognized as regex literal or comment start
p_req = searchio::compile(["require(", "/", "\"", "'", "`"]);
+ p_reqscript = searchio::compile(["require(", "</script", "/", "\"", "'",
+ "`"]);
p_newline = searchio::compile(["\n"]);
p_commentend = searchio::compile(["*/"]);
p_quotedouble = searchio::compile(["\""]);
@@ -68,12 +69,14 @@ let p_quotesingle: searchio::pattern = searchio::pattern {...};
@fini fn fini() void = {
defer searchio::finish(p_req);
+ defer searchio::finish(p_reqscript);
defer searchio::finish(p_newline);
defer searchio::finish(p_commentend);
defer searchio::finish(p_quotedouble);
defer searchio::finish(p_quotesingle);
};
+
// Add a connection frompath -> deppath to the dependency graph
// inputs are borrowed
fn dep_add(frompath: (str | void), deppath: str, graph: *dep_graph) void = {
@@ -103,6 +106,7 @@ fn dep_add(frompath: (str | void), deppath: str, graph: *dep_graph) void = {
};
};
+
// Recursively scan and add a file to the dependency graph
// inputs are borrowed
fn dep_scan(inputpath: str, graph: *dep_graph) void = {
@@ -149,22 +153,22 @@ fn dep_scan(inputpath: str, graph: *dep_graph) void = {
};
};
-// Is returned if the require() is part of a longer identifier
-type no = void;
// Parse the contents of a require() macro and return the file path.
// Return value has to be freed.
-fn read_require(in: io::handle, path: str) (str | no) = {
- // Check if require() is part of another identifier like my_require()
- io::seek(in, -9, io::whence::CUR)!;
+// Return void if require() is part of a longer identifier
+fn read_require(in: io::handle, path: str) (str | void) = {
const buf: [1]u8 = [' '];
- io::read(in, buf)!;
- io::seek(in, 8, io::whence::CUR)!;
- // this weird string contains all characters that are allowed in a js
- // source file but not in an identifier
- if (!strings::contains("\t\n\r !%&()*+,-./:;<=>?[]^{|}~", buf[0]: u32:
- rune))
- return no;
+ // Check if require() is part of another identifier like my_require()
+ if (!(io::seek(in, -9, io::whence::CUR) is io::error)) {
+ io::read(in, buf)!;
+ io::seek(in, 8, io::whence::CUR)!;
+ // this weird string contains all characters that are allowed in
+ // a js source file but not in an identifier
+ if (!strings::contains("\t\n\r !%&()*+,-./:;<=>?[]^{|}~",
+ buf[0]: u32: rune))
+ return void;
+ };
io::read(in, buf)!;
let broken = false;
@@ -181,6 +185,7 @@ fn read_require(in: io::handle, path: str) (str | no) = {
return ""; // will not be reached
};
+
// Kahn's algorithm https://en.wikipedia.org/wiki/Topological_sorting
// Return value has to be freed
fn sort_kahn(graph: dep_graph, entrypath: str) []size = {
@@ -222,3 +227,63 @@ fn sort_kahn(graph: dep_graph, entrypath: str) []size = {
};
return []; // will not be reached
};
+
+
+// Resolve require() and add files to the bundle
+// very similar to dep_scan()
+fn emit_bundled(inputpath: str, ofile: io::handle, graph: dep_graph, html: bool)
+ void = {
+ const ifile = os::open(inputpath)!;
+ defer io::close(ifile)!;
+ // Read until require or comment or quote
+ // if start of string literal etc was found (disabled require)
+ let disabled = false;
+ for (true) {
+ const m = searchio::search(ifile, ofile, p_reqscript);
+ if (m is size) {
+ const m = m: size;
+ if (m == 0) {
+ if (disabled == false) {
+ const p = read_require(ifile,
+ inputpath);
+ if (p is str) {
+ const p = p: str;
+ defer free(p);
+ const p = resolve_path_require(
+ p, inputpath);
+ defer free(p);
+ let i = 0z;
+ // could break if files are
+ // changed in race condition
+ for (graph[i].path != p) i += 1;
+ fmt::fprintf(ofile, "_tacker{}",
+ i)!;
+ } else fmt::fprint(ofile, "require(")!;
+ } else fmt::fprint(ofile, "require(")!;
+ } else if (m == 1) {
+ // </script
+ fmt::fprint(ofile, if (html) "<\\/script" else
+ "</script")!;
+ } else if (m == 2) {
+ // "/*", "//" or "/regex/"
+ fmt::fprint(ofile, "/")!;
+ const buf: [1]u8 = [' '];
+ if (io::read(ifile, buf) is io::EOF) break;
+ io::write(ofile, buf)!;
+ if (buf[0] == '/') {
+ searchio::search(ifile, ofile,
+ p_newline);
+ fmt::fprint(ofile, "\n")!;
+ } else if (buf[0] == '*') {
+ searchio::search(ifile, ofile,
+ p_commentend);
+ fmt::fprint(ofile, "*/")!;
+ } else disabled = true;
+ } else {
+ // '"', "'" or "`"
+ fmt::fprint(ofile, p_reqscript.original[m])!;
+ disabled = true;
+ };
+ } else break;
+ };
+};
diff --git a/searchio/searchio.ha b/searchio/searchio.ha
@@ -49,13 +49,14 @@ export fn compile(s: []str) pattern = {
} else break;
};
};
- return pattern { elems = p, original = s };
+ return pattern { elems = p, original = strings::dupall(s) };
};
export fn finish(p: pattern) void = {
for (let i = 0z; i < len(p.elems); i += 1) {
free(p.elems[i].data);
};
+ strings::freeall(p.original);
};
// reads until one of the end strings is read and pipes all read bytes to ofile
diff --git a/test-page/a.js b/test-page/a.js
@@ -9,3 +9,6 @@ function a() {
// this should throw a warning
console.log(require("test"));
};
+
+console.log(testm);
+window.testm = testm;
diff --git a/test-page/b.js b/test-page/b.js
@@ -1,4 +1,3 @@
-const c = "";
const c = require('./c');
module.exports = {
diff --git a/test-page/c.js b/test-page/c.js
@@ -1,3 +1 @@
-// console.log(require("./a.js")); // illegal -> circular dependency
-require("test-page/b.js");
-exports.msg = ":)";
+exports.msg = "</script> is safe.";