tacker

a simple web bundler
git clone https://tongong.net/git/tacker.git
Log | Files | Refs | README

commit 261c39eb69b74678c8249838fec1c7cd4a8d859d
Author: tongong <tongong@gmx.net>
Date:   Sat, 28 May 2022 22:16:15 +0200

idea draft

Diffstat:
AREADME.md | 41+++++++++++++++++++++++++++++++++++++++++
Atacker.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)!; +};