Skip to content

Commit

Permalink
feat: Support dynamic (Jinja-generated) classes (#8)
Browse files Browse the repository at this point in the history
* Support dynamic (Jinja-generated) classes

Run tailwindcss on the build dir, rather than input templates, so that dynamically-generated values are properly seen by tailwind.

* Don't use Tailwind watcher

Running Tailwind watcher from within Lektor watcher is unnecessary,
since we already have a watcher and know when to rebuild things, and
it leads to bugs, such as the Tailwind watcher leaking memory or
kicking in before the build is fully done.

* Fix tests and README

* Fix timeouts in test_server_*_update()

This was caused by the switch from having two watchers (lektor server and
tailwindcss) to just the server. Because we made writes as soon as
wait_for_server() returns, this would essentially catch the server in
a short window before it was done returning from a build, and it
wouldn't pick up the changes, so the rebuild would never
happen. Previously this was not a concern, because the CSS was watched
by one process, and other files by the other, and they delayed each
other, so there was never a chance to write immediately after the
rebuild was done. Now we sleep for a second before writing to prevent
that.

Note that this is purely an artefact of the testing process, and the
fact we can do it within milliseconds. This is not possible for a
human, so it doesn't come up in normal usage.
  • Loading branch information
mathrick authored Mar 8, 2024
1 parent 3d15eb3 commit ea66d2a
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 68 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ A Lektor plugin that adds Tailwind CSS to your project seamlessly.

```javascript
module.exports = {
content: ['./templates/**/*.{html,j2}'],
// './' refers to the lektor build output directory, NOT the project dir
content: ['./**/*.html'],
theme: {
extend: {},
},
Expand Down
83 changes: 21 additions & 62 deletions lektor_tailwind.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from lektor.pluginsystem import Plugin
from pytailwindcss import get_bin_path, install

__version__ = "0.1.2"
GRACEFUL_TIMEOUT = 5

__version__ = "0.1.3"

class TailwindPlugin(Plugin):
name = "lektor-tailwind"
Expand All @@ -22,86 +20,47 @@ def __init__(self, env, id):
self.css_path = config.get("css_path", "static/style.css")
self.input_css = os.path.join(self.env.root_path, "assets", self.css_path)
self.tailwind: subprocess.Popen | None = None
self.config_file = "tailwind.config.js"

def on_setup_env(self, **extra):
self.init_tailwindcss()

def init_tailwindcss(self):
if not os.path.exists(self.tailwind_bin):
install(bin_path=self.tailwind_bin)
filename = "tailwind.config.js"
if not os.path.exists(os.path.join(self.env.root_path, filename)):
if not os.path.exists(os.path.join(self.env.root_path, self.config_file)):
subprocess.run(
[self.tailwind_bin, "init"], check=True, cwd=self.env.root_path
)

def _run_watcher(self, output_path: str):
if not self.input_exists():
return

cmd = [
self.tailwind_bin,
"--input",
self.input_css,
"--output",
os.path.join(output_path, self.css_path),
"--watch",
]
if os.environ.get("NODE_ENV") == "production":
cmd.append("--minify")

self.tailwind = subprocess.Popen(cmd, cwd=self.env.root_path)
def _get_tailwind_args(self, output_path, *extra_args):
return [self.tailwind_bin,
"-c",
os.path.join(self.env.root_path, self.config_file),
"-i",
self.input_css,
"-o",
os.path.join(output_path, self.css_path),
*extra_args,]

def input_exists(self) -> bool:
return os.path.exists(self.input_css)

def should_minify(self) -> bool:
return not self.watch or os.environ.get("NODE_ENV") == "production"

def compile_css(self, output_path: str):
minify = ["--minify"] if self.should_minify() else []
subprocess.run(
[
self.tailwind_bin,
"-i",
self.input_css,
"-o",
os.path.join(output_path, self.css_path),
"--minify",
],
self._get_tailwind_args(output_path, *minify),
check=True,
cwd=self.env.root_path,
cwd=output_path,
)

def on_server_spawn(self, **extra):
self.watch = True

def on_server_stop(self, **extra):
self.watch = False
if self.tailwind is not None:
self.tailwind.terminate()
try:
self.tailwind.communicate(GRACEFUL_TIMEOUT)
except subprocess.TimeoutExpired:
self.tailwind.kill()
self.tailwind = None

def on_before_build_all(self, builder, **extra):
if not self.input_exists() or self.tailwind is not None or not self.watch:
return
self._run_watcher(builder.destination_path)

def on_before_build(self, builder, source, prog, **extra):
if source.source_filename != self.input_css:
def on_after_build_all(self, builder, **extra):
if not self.input_exists():
return

# The input stylesheet is being built. We don't want to let
# Lektor "build" it (i.e. copy it to the output directory),
# since that will potentially overwrite any Tailwind-compiled
# output that is already there.

# Here we monkey-patch Lektor's build program to disable it
prog.build_artifact = lambda artifact: None

# Instead, we run tailwind to compile the self.input_css to
# the output directory. (We skip this if we're already
# running tailwind in --watch mode, since, in that case, it
# will rebuild the CSS on it's own.)
if self.tailwind is None:
self.compile_css(builder.destination_path)
self.compile_css(builder.destination_path)
2 changes: 1 addition & 1 deletion tests/example/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const colors = require("tailwindcss/colors");

module.exports = {
content: ["./templates/**/*.html"],
content: ["./**/*.html"],
theme: {
extend: {
colors: {
Expand Down
8 changes: 4 additions & 4 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ class LektorServerFixture:
def __init__(self, port=9527):
proc = subprocess.Popen(
("lektor", "server", "-p", f"{port:d}"),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0,
Expand All @@ -101,9 +100,6 @@ def _close():
try:
self.sel.close()
proc.terminate()
proc.stdin.close()
proc.stdout.close()
proc.stderr.close()
proc.wait(5)
except (subprocess.TimeoutExpired, OSError):
proc.kill()
Expand Down Expand Up @@ -186,6 +182,8 @@ def test_server_rebuilds_css_when_template_updated(
lektor_server.wait_for_build()
assert ".flex" in output_css_path.read_text()

# Give the server a chance to pick up the changes before we start writing
time.sleep(1)
with open(tmp_project_path / "templates/layout.html", "a") as fp:
fp.write("""<div class="before:content-['SENTINEL']"></div>\n""")

Expand All @@ -200,6 +198,8 @@ def test_server_rebuilds_css_when_input_css_updated(
lektor_server.wait_for_build()
assert ".flex" in output_css_path.read_text()

# Give the server a chance to pick up the changes before we start writing
time.sleep(1)
with open(tmp_project_path / "assets/static/style.css", "a") as fp:
fp.write(".SENTINEL { color: red }\n")

Expand Down

0 comments on commit ea66d2a

Please sign in to comment.