I cobbled together a very light-weight process of bundling JS files built from Typescript. TLDR: I run tsc
to generate js/foo.js
for every src/foo.ts
. Then I bundle the JS files using esbuild
. Here's the rough dependency graph:
pkg/ js/ src/
a.js -> a.js -. .-> a.ts
b.js -> b.js --> .compiled -+-> b.ts
c.js -> c.js -' '-> c.ts
Because tsc
, like Java, compiles the whole code base at once, unconditionally, rather than individual source files, I've used a .compiled
file as an intermediate proxy to indicate tsc
compilation.
And here's the watered-down but standalone, functional, Makefile (slightly edited since original post):
.PHONY: bundle
bundle: .bundled
src_stems := a b c
ts_files := $(addsuffix .ts,$(addprefix src/,$(src_stems)))
js_files := $(addsuffix .js,$(addprefix js/,$(src_stems)))
pkg_files := $(addsuffix .js,$(addprefix pkg/,$(src_stems)))
.bundled: $(pkg_files)
touch $@
pkg:
mkdir -p $@
pkg/%.js: js/%.js | pkg
cp $< $@
$(js_files): .compiled
# Simulate running tsc
.compiled: $(ts_files)
mkdir -p js
touch $(js_files)
touch $@
.PHONY:
clean:
rm -rf pkg js .compiled .bundled
Everything works as expected upon make clean bundle
.
$ mkdir -p src
$ touch src/{a,b,c}.ts
$ make
However, I'm surprised at the behaviour when bundling after single file update. For example, running this after a clean build:
$ touch src/b.ts # I modify a single source
$ make
mkdir -p js
touch js/a.js js/b.js js/c.js
touch .compiled
cp js/b.js pkg/b.js <- Note a.js isn't being bundled!
cp js/c.js pkg/c.js
touch .bundled
And, if I run make
again, the missing file is caught up:
$ make
cp js/a.js pkg/a.js
touch .bundled
I tried debugging with make -d
but can't figure out why, when I have partial updates, it takes two runs to get a.js
bundled. Note also that it's always a.js
that's missed out, which I suppose is because it's the first source to be listed.
Some debug lines from make -d
:
Prerequisite 'js/a.js' is older than target 'pkg/a.js'.
...
Prerequisite 'js/b.js' is newer than target 'pkg/b.js'.
...
Prerequisite 'js/c.js' is newer than target 'pkg/c.js'.
The diagnostic for pkg/a.js
line is at odds with the rest. In fact, all the js/*.js
files will have the same timestamp.
What am I doing wrong? Are there other set of rules I should consider?
This line:
js/a.js js/b.js js/c.js: .compiled
is not sufficient. It tells make there's a prerequisite, but it doesn't provide any recipe. You want to have a recipe so that it's a full rule, not just a prerequisite declaration. Adding a semicolon would be enough (I believe):
js/a.js js/b.js js/c.js: .compiled ;
alternatively you could add something like this if that's more understandable:
js/a.js js/b.js js/c.js: .compiled
# do nothing
Just a note: it would be simpler to read your example (and realize it's correct) if you assigned the source files to a variable and used that variable, instead of rewriting all the files every time, where we have to carefully parse them to be sure they're not mistyped.