commit 261c39eb69b74678c8249838fec1c7cd4a8d859d
Author: tongong <tongong@gmx.net>
Date: Sat, 28 May 2022 22:16:15 +0200
idea draft
Diffstat:
A | README.md | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
A | tacker.ha | | | 84 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 125 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -0,0 +1,41 @@
+# tacker
+
+`tacker` takes your files and staples them together. The goal of this project
+is to be a simple web bundler independent of the disaster that is the modern
+npm ecosystem. Advanced bundling and optimization techniques are not in scope
+of `tacker` - try one of the bloated mainstream bundlers instead:
+- webpack (75 dependencies)
+- parcel (184 dependencies)
+- browserify (175 dependencies)
+- ...
+
+`tacker` was written as an experimental project in the new `hare` programming
+language.
+
+## features
+- entrypoints:
+ - HTML
+ - `<script src="...">` tags
+ - `<link rel="stylesheet" href="...">` tags
+ - binary data as base64 (`<audio>`, `<embed>`, `<img>`, `<source>`,
+ `<track>`, `<video>`)
+ - CSS
+ - other style sheets (`@import url(...)`)
+ - binary data as base64 (e.g. `background-image: url(...)`)
+ - JS
+ - a subset of CommonJS modules
+ - `require(...)` with relative
+ - `module.exports` and `exports`
+ - binary data as base64 through custom `requireBinary(...)` function
+
+CommonJS was chosen out of personal preference and its simplicity compared to
+ES Modules (tree-shaking optimizations enabled by ES Modules would not be
+implemented either way). The parser is rather simple though. To confirm to the
+CommonJS spec arbitrary javascript expressions (as the argument to `require()`)
+would need to be able to be evaluated at bundle-time. As `require` is just a
+value and not special syntax the same is the case for the whole program as
+every function could be possibly rebound to `require`. This requires the
+complete execution of the program at bundle-time to be able to reason about
+possible aliases to `require`. This is impossible and thus `require()` will be
+treated as special syntax. This implementation is thus wrong but should work
+for every sane usage of `require()`.
diff --git a/tacker.ha b/tacker.ha
@@ -0,0 +1,84 @@
+use encoding::hex;
+use encoding::utf8;
+use fmt;
+use getopt;
+use os;
+use rt;
+use slices;
+use strings;
+use types;
+
+// Inverse of strings::runes().
+// why is not something like this in the stdlib?
+// why does insertinto take a slice of pointers and not a pointer to a slice?
+fn runes_to_str(runes: []rune) str = {
+ let buffer = alloc([], len(runes) * 4): []u8: *[*]u8;
+ let index = 0z;
+ for (let i = 0z; i < len(runes); i += 1) {
+ const u = encoding::utf8::encoderune(runes[i]);
+ rt::memcpy(&buffer[index], &u[0], len(u));
+ index += len(u);
+ };
+ const s = types::string {
+ data = buffer,
+ length = index,
+ capacity = len(runes) * 4,
+ };
+ return *(&s: *const str);
+};
+
+// Input is borrowed, return value has to be freed.
+// test.js -> test.bundle.js
+// test.dot.js -> test.dot.bundle.js
+// no-ext -> no-ext.bundle
+fn file_name_bundled(ifile: str) str = {
+ let slc = strings::runes(ifile);
+ defer free(slc);
+ let lastdot = (len(slc) - 1): int;
+ for (lastdot >= 0 && slc[lastdot] != '.') {
+ if (slc[lastdot] == '/') {
+ lastdot = -1;
+ break;
+ };
+ lastdot -= 1;
+ };
+ // files without extension get the .bundle at the end
+ if (lastdot == -1) lastdot = len(slc): int;
+
+ static let b: []rune = [];
+ static let bptr: [7]*void = [&b: *void ...];
+ if (len(b) == 0) {
+ b = strings::runes(".bundle");
+ for (let i = 0z; i < len(b); i += 1) {
+ bptr[i] = &b[i];
+ };
+ };
+ slices::insertinto(&slc: *[]void, size(rune), lastdot: size, bptr...);
+ return runes_to_str(slc);
+};
+
+export fn main() void = {
+ const cmd = getopt::parse(os::args,
+ "simple web bundler",
+ ('f', "formats", "file formats to inline (comma seperated)"),
+ "input-file",
+ "output-file",
+ );
+ defer getopt::finish(&cmd);
+
+ const alen = len(cmd.args);
+ if (alen == 0)
+ fmt::fatal("At least the input file is as argument needed.");
+ if (alen > 2) fmt::fatal("Too many arguments passed.");
+ const ifile = cmd.args[0];
+ const ofile = if (alen == 1) {
+ // generate output file name from input file name
+ yield file_name_bundled(ifile);
+ } else {
+ yield strings::dup(cmd.args[1]);
+ };
+ defer free(ofile);
+
+ fmt::println(ifile)!;
+ fmt::println(ofile)!;
+};