Import maps are also worth exploring. Documentation isn't great but I've managed to ditch npm on some side projects and use import maps and CDNs instead. Worth it for small projects without a doubt.
If you use Visual Studio Code and stick a jsconfig.json in the root of your project, you can use JSDoc comments (and .d.ts files if you want) to get build-free type checking using its internal version of TypeScript:
Yeah that is annoying I must admit. You can create a nice server with just node but when you import something like a markdown parser or a sqlite driver then then your modules folder quickly fills up...
All those extraneous elements to Javascript are a real turn-off. Python: batteries mostly included. Javascript: feels like you need to drag a dozen suitcases along anytime you embark on any non-trivial project.
While the Python ecosystem trends towards a similar state as the JS ecosystem, at least no one yet needs to have any ideas about "tree shaking" and hype that as some kind of "new idea". When one needs to "shake out" code, because it has become too much, one should really think about not getting that code in there in the first place.
In the Python ecosystem some of the tooling does not exist, because it is not needed. Usually Python code is not shipped over network each time a website is called. Hopefully people keep dependencies of projects to a minimum, avoiding to have to fix their mistakes later by shaking out stuff. Python has an OK-ish module system, not great, but OK, compared to how JS started out. There is no need for 6-10 standards competing and requiring different "build tools" to make one huge cluster f of uglified code out of all the code of an application. Mind, it is 2023 and we still have no good way to tell the TypeScript compiler to simply spit out JavaScript code, that can immediately be served on a website, if the TS code has multiple modules. The JS ecosystem still suffers a lot from the not well thought through basis of the language and historical burden of that.
Python is far from perfect itself of course. Plenty of problems in its ecosystem as well.
Yeah, I get the impression some believe they're comparing apples with apples because they're both interpreted languages, but we don't exactly send Python scripts to millions of arbitrary clients, all of which execute them immediately.
Plausible but not sure it's entirely relevant. Most libs have parts that aren't used all the time. A lib that has no redundant parts is either a gem or a minuscule thing that will create a lot of miniscule deps.
> Hopefully people keep dependencies of projects to a minimum
This is not my experience of Python. Some of the tools I’ve installed via Homebrew have whole forests of dependencies. And don’t get me started on all the different Python versions they require.
> While the Python ecosystem trends towards a similar state as the JS ecosystem, at least no one yet needs to have any ideas about "tree shaking" and hype
I agree with your sentiment entirely about "When [...] it has become too much, one should really think about not getting that code in there in the first place," but that's really a problem with the NPM community in particular—not something that people who aren't NPM programmers can do anything about (and who, as people who work with JS, are even more annoyed by it than people hailing from communities that use other languages).
> Mind, it is 2023 and we still have no good way to tell the TypeScript compiler to simply spit out JavaScript code, that can immediately be served on a website
I guess it's actually necessary to point out the obvious here: TypeScript is not JS. The fact that TypeScript superficially resembles JS does not make the sins of the TypeScript team JS's responsibility. You could swap "TypeScript" for, say, FartTwist, an imaginary programming language that I just made up and doesn't resemble JS (or anything else that transpiles to JS with tools that have the same problem the TypeScript ones have), and the criticism would be exactly as applicable.
The big problem with the JS ecosystem is that it's filled with people who clearly don't like programming in JS, and yet they advertise themselves as part of that milieu. In fact, enough of them have banded together that they've managed to almost completely commandeer JS's public image, to the point that when JS is mentioned what comes to mind are their shenanigans, inevitably leading to discussions like this one.
I'm not sure why its getting negative points, totally valid take to have.
In my own experience though, python is far from batteries included. Ill tackle one vertical: packaging & dependency management. Historically you didn't get much out of the box for this problem.
You have pip, virtualenv, virtualenvwrapper, pyenv, pyvenv, pyenv-virtualenv, pipenv, et al. Almost a dozen different, and sometimes redundant, solutions for a
problem that historically python didn't solve natively for free. Poetry has drastically improved this problem, but not enough to really distinguish python from JS in a meaningful way IMO.
Anecdotally I have had the exact opposite experience. JavaScript projects generally work after running `npm install` and then running a single build command.
Python projects (in my personal experience) require jumping through a lot of hoops to get dependencies working correctly.
It could be my experience with Python is just bad luck!
It wasn't done in a nice way, but the kitchen-sink builds of PHP were quite nice as dependency-free environments back in the day. Image manipulation and all kinds of parsing and database connectivity stuff built in.
My experience with dependency-"free" web python is limited to small projects using Bottle (one downloaded .py file) and sqlite for tiny sites.
I think this holds only if you opt-in to tools like WebPack, Babel, Next.js, React, and the like - or have to work in a project where someone else opted-in to these tools.
Deno is a single portable binary file that can be embedded in app bundles and invoked on users' machines. That's easier said than done with Python.
My own experience is that anytime you have a situation where the included batteries aren't enough (which has been every time), you're left to figure out eggs vs wheels vs distutils vs setuptools vs easyinstall vs pip vs pip3 vs pip3.7 vs pip3.8 vs pipx vs pip-tools vs poetry vs pyenv vs pipenv vs virtualenv vs venv vs conda vs anaconda vs miniconda.
Frankly, if you're just writing a local CLI tool with Node, it's not really an issue. Most of the build tool features listed in the article aren't needed for a local JS runtime. Node is even getting a built-in testing framework, and Deno can run Typescript without an intermediate step!
Build tools for JS solve problems that other languages don't have to worry about. You can't use Python (directly) in a browser which means none of these things are important:
1. Bundle size. This doesn't really matter when you're just running it locally. Maybe you want the final binary to be somewhat small, but it could still be many dozens of megabytes and it'd be a non-issue. If a website was like 50MB, it'd be incredibly slow.
2. Platform compatibility. Every user is running a slightly different environment. Gotta support different browsers, or older ones? Now you need to polyfill the language features to exactly what the browser supports. Compiled languages don't have to worry about that at all, and even with Python, you're mostly just worried about the major language version a user might have installed (iirc).
3. Anything related to writing UIs. In JS, you're writing for the browser, which has a pretty limited set of APIs for writing complex, interactive UIs. Hardly anything to help write interactive declarative or reactive UIs. On top of that, since the site is always remote, pretty much everything has a round-trip to a server involved. This means you may start pulling in dependencies to help solve these problems. In Python, you probably just choose a UI framework like QT and it gives you most everything out of the box.
My point is just that we tend to compare JavaScript to other languages without really considering the unique environment JS gets deployed to. It's really not comparable. If Python (or any other language) was running in a browser, you'd have a huge new class of problems for that language to solve which it just didn't need to care about before.
I feel like the meaningful comparisons to JS are related to language syntax, type system, local runtime performance, dependency management, etc. And those topics are a lot more subjective!
Platform compatibility is much easier in web compared to python or anything else native. You only have the browser to worry about and nothing else, and the variation between browsers is there but much less than the variations between cpu architectures, oses, languages etc in native apps. The variation between the two biggest ones, firefox and chrome is even narrower yet we still get "only works in chrome" or worse bundling an entire copy of a very specific version of chrome along with your app in for example Electron.
> Build tools for JS [sic] solve problems that other languages don't have to worry about.
Explain that to all the people writing browser extensions which by their very nature have an entirely different set of relevant engineering concerns in contrast to Web apps delivered by HTTP—and yet the same minification tools and compatibility shims still abound for as far as the eye can see.
Skipping a build system is my default for new JavaScript these days. The main suggestion I would add is using git submodules[0] for dependencies. It's a bit unwieldy and I always have to look up the syntax, but works pretty well.
You can have something like a `lib` directory in your project and place submodules for other projects in there. The dependencies need to provide some sort of a prebuilt artifact in the repo that you can import (or natively support ES Modules), but I've had good luck so far.
I'll also shout out jsdelivr[1] which is great for pulling dependencies directly from github.
> I’d love more tips for no-build-system javascript
1. MDN has a comprehensive guide on JavaScript modules [0]
2. A build system free way to build interactive websites could be to combine libraries like htmx[1] and or lit[2] or just the sub package lit-html[3]. Or just go with native web components and a bit of AJAX.
+1 for Lit. It seems like they're mostly recommending that you use TypeScript and a build system, but you can 100% still just pull the library from a CDN with native JS modules [0].
It leans into a lot of the web components spec so the library is pretty small, and the templating system is just ES6 tagged template literals parsed by the browser's DOM parser and any embedded variables are updated dynamically [1].
I think Lit is still recommending using web dev server (https://modern-web.dev/docs/dev-server/overview/), which no, its not a build system, but still a bit of a departure from a simple web server. And that's only needed to resolve imports that aren't a full relative path (like if I want to import Lit, I don't have to do import node_modules/lit/whatever). Someone else mentioned import maps here, and it's now supported in all browsers if you include the Safari preview release. I haven't tried it yet because I've been hung up on it being Chrome only until now, but it should complete the no-build experience. Except yeah, Typescript, but if you use it, you can just leave it watching in the background without much intervention.
I've used `tsc --watch` as an almost-no-build-step way to write plain TypeScript with better structure (separate files, etc) for projects where dependencies aren't necessary, which works reasonably well with a few quirks, but I wish there were something more purpose-built for the task. TypeScript running natively in the browser would be amazing but I doubt that'll ever happen.
I wonder how far fetched it would be to have just the syntax of Typescript on the browser without type-checking. We'd have to use tsc locally only for the typechecking, without the need of a build process, but that would be enough for me.
I know there's JSDoc but the normal syntax is so much better.
I use an in-browser typescript loader (no type checking, just stripping definitions -> ~10x faster than tsc), fast enough for most personal projects I do recently
The appeal of JavaScript when it was invented was its immediacy. No longer did you have to go through an edit-compile-debug loop, as with Java, or even an edit-upload-debug loop as with a Perl script, to see the changes you made to your web-based application. You could just mash reload on your browser and Bob's your uncle!
The JavaScript community, in all its wisdom, reinvented edit-compile-debug loops for its immediate, dynamic language and I'm still assmad about it. So assmad that I, too, forgo all that shit when working on personal projects.
(The enterprise fucking loves React and Vue. Can't avoid it at work.)
A thing that I liked when I started out was the fact that you could actually read all of the JavaScript on web pages. You found something interesting? Just fetch the source or open your dev tools (firebug!) and look at the code.
There's probably merit in minification and compressing code though. But for the types of projects OP is talking about it's going to be single digit KB of JS anyways.
> The appeal of JavaScript when it was invented was its immediacy.
You can still do that. Nothing has changed. It's probably gotten even easier. Have fun implementing a complex UI without some sort of component Framework though.
You can easily build complex UIs without a framework. Chrome DevTools, Chrome OS, Photoshop, Firefox and other very complex apps have been built with web components.
Web Components aren’t an alternative to React so much as a complementary technology. Yes, you can build UIs without a reactive framework but it will involve compromises in expressivity, code organization, and whatever the Greenspun equivalent is for React.
Web components do not imply a lack of reactivity. JavaScript accessors, observed attributes, and a declarative template library give you as much reactivity as React. Photoshop and Chrome are built with reactive web components.
The precursor to Web Components was XBL (NB: only ever implemented in Gecko), which was used extensively in the Firefox UI. (The Firefox product consists of literally hundreds of thousands of lines of JS powering the app UI—and many of its background components.) Gecko was doing Electron-style apps before Electron (or Chrome and JITted JS engines) ever existed.
> But my experience with build systems (not just Javascript build systems!), is that if you have a 5-year-old site, often it’s a huge pain to get the site built again.
5 years? More like 5 minutes in my experience. I probably spend 50% of my time trying to figure out why the build system is breaking, again.
One of my favourite hacks I did recently was in 2020 when soup.io was unexpectedly discontinued with short notice; to promote my scraping framework, Skyscraper, I quickly whipped up a content downloader. It produced an archive of assets, and I also added a rudimentary browser.
The beauty of it was that it was a single static HTML file, copied verbatim to the archive. The only moving part, apart from the actual images/posts/videos, was a file called soup.js, which was just `window.soup=` plus the content metadata as JSON.
I didn’t even bother to have a separate JS file for the actual code, much less a build system. I put the JS right into a `<script>` tag in the HTML. I used ES modules, HyperApp as a minimalistic React/Redux workalike, and mini.css for some sane default styling.
I was amazed at how far I could get with such a rudimentary tooling. 117 SLOCs of HTML+JS, working equally well served from a server and from a local filesystem, complete with pagination, permalinks, and a jump-to-page dialog.
Here it is in action, serving a friend’s soup archive: https://soup.tomash.eu/archive (warning: some content can be touchy and/or NSFW). View source for a glimpse of how it works.
> I was amazed at how far I could get with such a rudimentary tooling.
Are you just 1 person working on a project?
A lot of "best practices" aren't particularly needed when you're flying solo and know your code and mental processes inside and out.
Where this starts to get hairy is when you have multiple developers all trying to understand one person's mental model of how the code is structured, all in one file that they have to work in.
A lightning rod of an idea I’m sure, but would it help if typescript was accepted by browsers but ignored? Then I can write my typings in-line rather than in comments, and check them, but not have to transpile.
I guess “enum” as a special snowflake would fail this.
Where I’m coming from is that I can live without most build steps for a small project… but I can’t live without typescript.
Getting older projects to run was a huge pain for me when I started working with JavaScript, but I have found two easy steps to get around this:
1. Use nvm + an .nvmrc file in your project to pin the major version of Node.js you are using. I recently got a five year old Node 8 project up and running with no issues using this method.
2. Try to avoid packages that has binary dependencies (like node-sass). 99% of binary issues are fixed when using the correct Node version, the only other problem that can occur is if you switch architecture (eg. x86 => ARM).
Why rely on nvm to pin the NodeJS version you are using? That's natively supported as a field in your package.json [0], assumimg you're using npm/yarn/pnpm (you are).
Example:
{
"engines": {
"node": ">=0.10.3 <15"
}
}
Also, god forbid you are actually running anything using Node 8 and whatever dependencies you have there, that's a recipe for security disaster.
The truth is that to use the NodeJS ecosystem we must have the discipline to keep projects up-to-date. I personally have a policy to update all my NodeJS projects at least yearly, even if they are abandoned, this includes all dependencies and the NodeJS version. Because I do it often most of the times there are not many problems to keep it working, and consequently updating is fast, but if I leave it for a few years and then come back I'll be in for a (bad) surprise.
Mostly because you want your project to just work. You've tested your project with a certain version of Node, and you want to ensure that you use the same exact version when you come back to the project in a year and want to perform some minor updates. You can set the node constraint in package.json, but that doesn't install the correct version of Node for you, unlike nvm.
Well that makes sense, as long as the developer running the code has nvm installed. Does nvm-windows read and install the versions in these nvmrc files? If it does your strategy is indeed pretty cool and I might start doing it myself. I wonder if there's any way to force specific versions of NodeJS without relying on nvm or some compatible tool.
edit: someone here in this thread suggested Volta [0] which seems literally like what I was looking for. No need for the nvmrc as it simply follows what's in the engine property of the package.json. Even better is that it's not a weird massive shell script but instead an actual program written in Rust. I'll have to try it out sometime.
We recently solved this problem org-wide with Volta. It allows pinning both the Node and package manager (npm, yarn, etc.) version in each project's package.json file.
Even better, Volta automatically uses whatever node and package manager versions are declared in package.json -- no explicit call required. In other words, `node -v` and `npm -v` always return the version specified in package.json. It's been helpful for working with hundreds of repos and keeping CI and local dev synced on which versions to use automatically.
How does fnm compare to volta [0] if you don't mind me asking? I see both are written mostly in Rust instead of being bloated shell scripts, although I'm not sure why choose one over the other.
AMD was a great module system precisely because the build step was optional. Running the build helped on large projects (for bundling, minifying, etc.), but you could literally deploy your frontend's `src` directory to production and expect it to work as-is. A nice side-effect was that local development only required a simple file http service.
I'm hoping ES module tooling, import maps, etc. get us back to that point.
Indeed. I miss AMD and tooling from that time, it was all pretty straightforward and browser focused. Combined with PHP and you could do some pretty neat things, too.
Regarding Tailwind 3, there's no longer a pre-generated CSS option, but a build process isn't strictly necessary. You can load the library through a CDN URL [0] and the classes will be generated in the browser. It's not recommended for production use though, so if Tailwind 2 has everything you need it's probably the better choice.
The fact that they added a breaking change to the way node runs, with no information in the error message about how to fix it, or even what to fix is unbelievable. We're still running node 16 just because some libraries don't work with the new SSL system.
Have you used Vite before? If you have a simple project it's surprisingly fast and painless. What's the "additional overhead" you're talking about? I experienced that with Webpack, but not Vite.
The author even shouts out esbuild as being "a little more stable", which Vite uses under the hood.
I think he just means you have to build before deploying and while developing. Even if that build takes fractions of a second and is trivially scaffolded and automated in a modern context, it’s still technically an extra step. There was a time I might have agreed with that take but I’ve since embraced the build since unless we’re talking about the kind of JavaScript you’d embed in a single blog article and then promptly forget about, chances are you’ll want to introduce build tools eventually.
I'm definitely a fan of Vite personally. You can tell the project prioritized a focus on developer experience outcomes and its very well executed in my opinion.
But the core package is ~27k SLOC with upwards of 40 dependencies. Thats an indirect, but poignant statement illustrating how much work goes into solving just a tooling problem. And while it may hide some of the overhead in its own abstraction, which it does a fantastic job of, the overhead is still there and it can still break in arcane ways.
I think the spirit of the question of "why do we even need Vite in the first place?" is whats really being explored in the original post.
I certainly don't shy away from build tools at all, but I do often try to start projects without them just as an exercise to see if they really ever end up being needed. Especially when its so easy to drop-in something like Vite after the fact if its necessary and/or clearly adds an outsized return on investment.
In black and white terms, sure, it’s still a build process, but I second the recommendation to try Vite - the overhead is minimal and the expressive power build chains enable more than justify the overhead. I say this as someone who still writes plenty of vanilla JavaScript and was long reticent to adopt build chains into my workflow.
Vite is so lightweight it feels more like http server than a build system.
The reason why it’s needed at all is it adapts npm packages that are not es modules. It gives you access to all of npm while requiring very little in return.
You can even turn off any transpilation/minification if you want to it to be more 1:1.
If anyone's looking for something like a framework that works with buildless workflows you might check out the project I work on: Lit ( https://lit.dev )
Lit gives you reactive components, declarative HTML templates, runtime encapsulated DOM and styles, interoperability with frameworks and HTML via web components, and a lot more - with no required build tools at all.
We take great care to make sure our libraries are usable without build tools. We support plain JS (in addition to TypeScript). Our libraries work straight from CDNs like Unpkg. When installed locally you can use a dev server that rewrites bare module specifiers or use an 8-line import map to make all of our core packages importable (we carefully keep all of our import specifiers compatible with the web and compact import maps). Components are inspectable with regular DevTools. You can even write components right in DevTools, or in a script tag in plain HTML.
I honestly wish this weren't a unique selling point of Lit, but as far as I can tell, of the "major" frameworks or component libraries out there (including React, Vue, Angular, Solid, Ember, Svelte, Stencil, Preact, and more) we're the only ones that fully work with plain JS within our regular mainstream usage patterns.
I've been doing web dev for 20 years and Lit has finally allowed me to achieve my dream of what I've imagined web dev should've been like this entire time. Thank you for your work, please keep it up!
We used Lit for a project a while ago and I definitely vouch for it.
We were embedding the component into a legacy application and needed to use a build tool for older browser compatibility but it was a breeze to work with even with the extra complication our build setup added.
Getting back to basics and targeting what most modern browsers support out of the box is a huge plus.
Thanks for the reference, interesting, I will have a look.
vuejs can work without build system though. it's mentioned in the article, and I used to wwork that way. Eventually i made my own (minimal) build system though using quickjs and esbuild. Precompiling templates and minimising can be useful.
I've been looking for a modern JS library that doesn't require a build step, and I've always skipped over Lit because I thought it depended on a build until this comment.
Is there any documentation about using it without a build? All the documentation and tutorials seem to assume a build step. I've found several different "getting started" entrypoints on the site, and they all begin with "npm install" and "import {LitElement, html} from 'lit';" which won't work without a build step, correct?
Some of this depends on what you consider a build step. I generally don't consider installing from npm a build step. I also don't really consider rewriting import specifiers a build step - it's just locating files on disk.
Regardless, we do have some workflows for completely "tool-less" (or local-tool-less).
- You can import directly from JS module CDNs like unpkg.com, ie:
import {LitElement, html} from 'https://unpkg.com/lit@2.6.1/index.js?module';
Ultimately we're not really dogmatic about things and support a wide variety of tools. The thing we care about is that tool-less development is possible, and that you can use standard semantics and tools with no special configuration (ie, regular Node resolution is all that's required locally, we stick to standard ES2020).
>I generally don't consider installing from npm a build step. I also don't really consider rewriting import specifiers a build step - it's just locating files on disk.
Sorry, maybe this is just my JS ignorance, but I don't understand how this runs without a build step.
If I run npm -i lit, it puts lit into node_modules. But then if I put "import {LitElement, html} from 'lit';" into a JS file and run a dumb static webserver (like python3 -m http.server), what's linking "from 'lit'" to the necessary files in node_modules?
I just tried running the first tutorial[0] verbatim after npm installing lit to the same directory, and I get:
Uncaught TypeError: The specifier “lit” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”. my-element.js:1:31
I'd love to use this, I'm just not following how this works.
Is it designed to run server-side rather than for static sites?
So you have four-ish options to tell the browser where to look for 'lit':
1. Use a server that will use the node module resolution algorithm and transforms it to a relative specifier as it encounters the import (transform/build-ish?)
2. Use experimental "import maps" to go fully buildless to tell the browser where to point that bare module to (buildless)
3. Bundle all your code (build step)
4. Use a bundle from a CDN
Here are some examples of each:
Server that transforms import specifiers (lit.dev does this by default):
if you change your application code on disk and reload the page and it works, then there's no build step. 1-time setup actions like installing from NPM aren't considered.
I just want to point out there is a great alternative from Microsoft called `fast-design`[0] which provides a more fleshed out experience than Lit does, in terms of out of the box components.
It does have lower level packages if you don't want ready made components too
FAST is kind of like a Lit-clone (though it patches global prototypes) plus a design system base.
There are lots of design systems built with Lit that you can chose from instead of us including our own: Shoelace, Adobe Spectrum, Redhat Patternfly, IBM Carbon, Material Web Components and more.
First of all, thank you for the work you have done on this, I love lit, use it all the time at work.
One thing I struggle with from time to time is that if I have a web component created in lit, and somewhere inside I have a dependency that has some CSS, that CSS doesn’t seem to work when the web component is consumed by JS.
The only workaround I found was to override the behaviour and instead of returning a shadow dom, return the component on its own, but that has its own downsides.
Most devs have no idea what lit-html + DOM event + sub 100 lines glue code (mvc) is capable of. No useThis useThat non-sense, just call render() manually, really, it's not a big deal like at all.
Lit-Html is a templating library similar to say Mustache or something like that but I would argue much more expressive. You can see a demo of how it looks here https://www.npmjs.com/package/lit-html
Lit is another library that is made up of a couple of things one of which is lit-html but is closer to a React or Vue by way of comparison.
Some noticeable differences between it and other view layer libraries include the fact that it was written by a team who are arguably much closer to actual browsers engineers and it uses native Web Components. The side-effect of this is that it’s incredibly fast and lightweight. I tend to think of it as the 5kb developer experience enhancement that takes Web Components as a primitive and makes them an actual pleasure to work with.
There are also a series of enhancement packages that are also totally opt in if you want more out of the box like internationalisation, client side routing etc.
If you go to lit.dev the documentation is actually pretty great and it’s probably the easiest way to get started.
How much do I have to know about web components before using Lit?
But thank you for your response. Did not know that I was looking for Lit all these time. Every time I want to dabble into web dev, I get frustrated by multitude of tools need to setup first. All I wanted was to import a library using script tag and start building.
Hey, long time lit-html fan here. Had always held off on using Lit itself because from the documentation it felt like build tools were required. Great to see that's not the case! Definitely going to check this out.
> ... we're the only ones that fully work with plain JS
Vue (v2, don't know about v3) can also work without a build script, either with a render function, or a "template" (an string that mixes HTML with expressions).
I really like Lit. It makes you in most cases write better reusable components, though you should still check all the properties to see if they are too reliant on some property.
We started using it because we knew it could be embedded into any other framework, we are using two others in the company. But are starting to use it for SPA also. Glad to see it growing quickly.
Thinking about possibly reworking our SPA to use Phoenix LiveView most of the abstractions are there already.
When I was learning HTML and JS in the early 2000s, most web pages had hand-written source code, and most JS I encountered was hand-crafted and unminified. So I learned programming by just reading the code of the pages I was on.
That's the main reason for me not to use a build system. You lose that "transparency" and accessibility of the code. With "raw" HTML/JS, the user can just copy paste your HTML/JS (often it was just 1 file for both combined!) into notepad, save as HTML and have their own website/"app"!
At least, that's how I felt until I got comfortable with TypeScript, and now I will refuse to use raw JS even for small projects... Alas! (Would be nice if V8 could strip type annotations and just run the plain JS hidden underneath... would also help with pasting TS snippets into the dev console!)
It seems just a few years ago when I was testing out an early version of react, and it had the JSX compiler as a header include. Worked well enough, and with more modern JS supported by default, I wouldn't mind a newer version of this.
Instead of the increasingly silly attempts of coming up with weirder ways of doing server side rendering.
The newer (or perhaps still the same!) version of this is to add `babel-standalone` to the page [0] and put the `type="text/babel"` attribute on any `<script>` tags where you want to use JSX.
I’ve been using Fly.io’s default Go template lately since it has binary embeds by default and doesn’t incur any build system. I can’t explain how liberating it is to use vanilla JavaScript and CSS without any additional build process to develop a webapp. My entire deployment cycle is just building a go binary and pushing it. I can’t imagine going back. Even using Vue or React is just as easy as including a CDN or bundling a minified source with the binary.
I’ve had good luck splitting the difference by writing custom, dependency-free build scripts. You never get this accelerated code rot when you depend only on a mainstream language, it’s pretty trivial to implement a primitive version of bundling, inlining, macros, etc, and being primitive is far less limiting when you’re developing your build script hand-in-hand with your code.
Some things can’t be done like this, like TypeScript, for which I keep it to a minimum, look for stability, and often include a full copy in revision control.
186 comments
[ 2.5 ms ] story [ 258 ms ] threadMe too. And the tips already in here are pretty good!
Import maps are also worth exploring. Documentation isn't great but I've managed to ditch npm on some side projects and use import maps and CDNs instead. Worth it for small projects without a doubt.
Try to use as few dependencies as possible.
You don't need a framework--components are achievable without vue and react etc.
Server side rendering and VDom are really not needed for most projects.
Think of Programming in JavaScript, CSS and F5 rather than Typescript and SASS as the equivalent of Marcus Aurelius' Stoicism.
Hahaha. You joke.
In the Python ecosystem some of the tooling does not exist, because it is not needed. Usually Python code is not shipped over network each time a website is called. Hopefully people keep dependencies of projects to a minimum, avoiding to have to fix their mistakes later by shaking out stuff. Python has an OK-ish module system, not great, but OK, compared to how JS started out. There is no need for 6-10 standards competing and requiring different "build tools" to make one huge cluster f of uglified code out of all the code of an application. Mind, it is 2023 and we still have no good way to tell the TypeScript compiler to simply spit out JavaScript code, that can immediately be served on a website, if the TS code has multiple modules. The JS ecosystem still suffers a lot from the not well thought through basis of the language and historical burden of that.
Python is far from perfect itself of course. Plenty of problems in its ecosystem as well.
<https://en.wikipedia.org/wiki/History_of_Python>
This is not my experience of Python. Some of the tools I’ve installed via Homebrew have whole forests of dependencies. And don’t get me started on all the different Python versions they require.
I agree with your sentiment entirely about "When [...] it has become too much, one should really think about not getting that code in there in the first place," but that's really a problem with the NPM community in particular—not something that people who aren't NPM programmers can do anything about (and who, as people who work with JS, are even more annoyed by it than people hailing from communities that use other languages).
> Mind, it is 2023 and we still have no good way to tell the TypeScript compiler to simply spit out JavaScript code, that can immediately be served on a website
I guess it's actually necessary to point out the obvious here: TypeScript is not JS. The fact that TypeScript superficially resembles JS does not make the sins of the TypeScript team JS's responsibility. You could swap "TypeScript" for, say, FartTwist, an imaginary programming language that I just made up and doesn't resemble JS (or anything else that transpiles to JS with tools that have the same problem the TypeScript ones have), and the criticism would be exactly as applicable.
The big problem with the JS ecosystem is that it's filled with people who clearly don't like programming in JS, and yet they advertise themselves as part of that milieu. In fact, enough of them have banded together that they've managed to almost completely commandeer JS's public image, to the point that when JS is mentioned what comes to mind are their shenanigans, inevitably leading to discussions like this one.
In my own experience though, python is far from batteries included. Ill tackle one vertical: packaging & dependency management. Historically you didn't get much out of the box for this problem.
You have pip, virtualenv, virtualenvwrapper, pyenv, pyvenv, pyenv-virtualenv, pipenv, et al. Almost a dozen different, and sometimes redundant, solutions for a problem that historically python didn't solve natively for free. Poetry has drastically improved this problem, but not enough to really distinguish python from JS in a meaningful way IMO.
Python projects (in my personal experience) require jumping through a lot of hoops to get dependencies working correctly.
It could be my experience with Python is just bad luck!
My experience with dependency-"free" web python is limited to small projects using Bottle (one downloaded .py file) and sqlite for tiny sites.
And a Tcl "starkit" aaages ago.
Deno is a single portable binary file that can be embedded in app bundles and invoked on users' machines. That's easier said than done with Python.
Build tools for JS solve problems that other languages don't have to worry about. You can't use Python (directly) in a browser which means none of these things are important:
1. Bundle size. This doesn't really matter when you're just running it locally. Maybe you want the final binary to be somewhat small, but it could still be many dozens of megabytes and it'd be a non-issue. If a website was like 50MB, it'd be incredibly slow.
2. Platform compatibility. Every user is running a slightly different environment. Gotta support different browsers, or older ones? Now you need to polyfill the language features to exactly what the browser supports. Compiled languages don't have to worry about that at all, and even with Python, you're mostly just worried about the major language version a user might have installed (iirc).
3. Anything related to writing UIs. In JS, you're writing for the browser, which has a pretty limited set of APIs for writing complex, interactive UIs. Hardly anything to help write interactive declarative or reactive UIs. On top of that, since the site is always remote, pretty much everything has a round-trip to a server involved. This means you may start pulling in dependencies to help solve these problems. In Python, you probably just choose a UI framework like QT and it gives you most everything out of the box.
My point is just that we tend to compare JavaScript to other languages without really considering the unique environment JS gets deployed to. It's really not comparable. If Python (or any other language) was running in a browser, you'd have a huge new class of problems for that language to solve which it just didn't need to care about before.
I feel like the meaningful comparisons to JS are related to language syntax, type system, local runtime performance, dependency management, etc. And those topics are a lot more subjective!
Explain that to all the people writing browser extensions which by their very nature have an entirely different set of relevant engineering concerns in contrast to Web apps delivered by HTTP—and yet the same minification tools and compatibility shims still abound for as far as the eye can see.
Be mindful of your blindspots.
You can have something like a `lib` directory in your project and place submodules for other projects in there. The dependencies need to provide some sort of a prebuilt artifact in the repo that you can import (or natively support ES Modules), but I've had good luck so far.
I'll also shout out jsdelivr[1] which is great for pulling dependencies directly from github.
[0]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
[1]: https://www.jsdelivr.com/
1. MDN has a comprehensive guide on JavaScript modules [0]
2. A build system free way to build interactive websites could be to combine libraries like htmx[1] and or lit[2] or just the sub package lit-html[3]. Or just go with native web components and a bit of AJAX.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...
[1] https://github.com/bigskysoftware/htmx
[2] https://github.com/lit/lit
[3] https://github.com/lit/lit/tree/main/packages/lit-html
It leans into a lot of the web components spec so the library is pretty small, and the templating system is just ES6 tagged template literals parsed by the browser's DOM parser and any embedded variables are updated dynamically [1].
[0] https://lit.dev/docs/getting-started/#use-bundles
[1] https://lit.dev/docs/templates/overview/
I know there's JSDoc but the normal syntax is so much better.
EDIT: Looks like there's a proposal: https://github.com/tc39/proposal-type-annotations
Typescript is baffling-ly complex to use compared to something like python.
What's complex about that?
The JavaScript community, in all its wisdom, reinvented edit-compile-debug loops for its immediate, dynamic language and I'm still assmad about it. So assmad that I, too, forgo all that shit when working on personal projects.
(The enterprise fucking loves React and Vue. Can't avoid it at work.)
There's probably merit in minification and compressing code though. But for the types of projects OP is talking about it's going to be single digit KB of JS anyways.
You can still do that. Nothing has changed. It's probably gotten even easier. Have fun implementing a complex UI without some sort of component Framework though.
This site can’t be reached
5 years? More like 5 minutes in my experience. I probably spend 50% of my time trying to figure out why the build system is breaking, again.
The beauty of it was that it was a single static HTML file, copied verbatim to the archive. The only moving part, apart from the actual images/posts/videos, was a file called soup.js, which was just `window.soup=` plus the content metadata as JSON.
I didn’t even bother to have a separate JS file for the actual code, much less a build system. I put the JS right into a `<script>` tag in the HTML. I used ES modules, HyperApp as a minimalistic React/Redux workalike, and mini.css for some sane default styling.
I was amazed at how far I could get with such a rudimentary tooling. 117 SLOCs of HTML+JS, working equally well served from a server and from a local filesystem, complete with pagination, permalinks, and a jump-to-page dialog.
Here it is in action, serving a friend’s soup archive: https://soup.tomash.eu/archive (warning: some content can be touchy and/or NSFW). View source for a glimpse of how it works.
Are you just 1 person working on a project?
A lot of "best practices" aren't particularly needed when you're flying solo and know your code and mental processes inside and out.
Where this starts to get hairy is when you have multiple developers all trying to understand one person's mental model of how the code is structured, all in one file that they have to work in.
I guess “enum” as a special snowflake would fail this.
Where I’m coming from is that I can live without most build steps for a small project… but I can’t live without typescript.
1. Use nvm + an .nvmrc file in your project to pin the major version of Node.js you are using. I recently got a five year old Node 8 project up and running with no issues using this method.
2. Try to avoid packages that has binary dependencies (like node-sass). 99% of binary issues are fixed when using the correct Node version, the only other problem that can occur is if you switch architecture (eg. x86 => ARM).
Example:
Also, god forbid you are actually running anything using Node 8 and whatever dependencies you have there, that's a recipe for security disaster.The truth is that to use the NodeJS ecosystem we must have the discipline to keep projects up-to-date. I personally have a policy to update all my NodeJS projects at least yearly, even if they are abandoned, this includes all dependencies and the NodeJS version. Because I do it often most of the times there are not many problems to keep it working, and consequently updating is fast, but if I leave it for a few years and then come back I'll be in for a (bad) surprise.
[0]: https://docs.npmjs.com/cli/v9/configuring-npm/package-json?v...
edit: someone here in this thread suggested Volta [0] which seems literally like what I was looking for. No need for the nvmrc as it simply follows what's in the engine property of the package.json. Even better is that it's not a weird massive shell script but instead an actual program written in Rust. I'll have to try it out sometime.
[0]: https://volta.sh/
Even better, Volta automatically uses whatever node and package manager versions are declared in package.json -- no explicit call required. In other words, `node -v` and `npm -v` always return the version specified in package.json. It's been helpful for working with hundreds of repos and keeping CI and local dev synced on which versions to use automatically.
[0]: https://github.com/volta-cli/volta
I'm hoping ES module tooling, import maps, etc. get us back to that point.
[0] https://tailwindcss.com/docs/installation/play-cdn
The fact that they added a breaking change to the way node runs, with no information in the error message about how to fix it, or even what to fix is unbelievable. We're still running node 16 just because some libraries don't work with the new SSL system.
It's a massive pain.
The author even shouts out esbuild as being "a little more stable", which Vite uses under the hood.
But the core package is ~27k SLOC with upwards of 40 dependencies. Thats an indirect, but poignant statement illustrating how much work goes into solving just a tooling problem. And while it may hide some of the overhead in its own abstraction, which it does a fantastic job of, the overhead is still there and it can still break in arcane ways.
I think the spirit of the question of "why do we even need Vite in the first place?" is whats really being explored in the original post.
I certainly don't shy away from build tools at all, but I do often try to start projects without them just as an exercise to see if they really ever end up being needed. Especially when its so easy to drop-in something like Vite after the fact if its necessary and/or clearly adds an outsized return on investment.
In this case the A in YAGNI stands for “Are”.
The reason why it’s needed at all is it adapts npm packages that are not es modules. It gives you access to all of npm while requiring very little in return.
You can even turn off any transpilation/minification if you want to it to be more 1:1.
Lit gives you reactive components, declarative HTML templates, runtime encapsulated DOM and styles, interoperability with frameworks and HTML via web components, and a lot more - with no required build tools at all.
We take great care to make sure our libraries are usable without build tools. We support plain JS (in addition to TypeScript). Our libraries work straight from CDNs like Unpkg. When installed locally you can use a dev server that rewrites bare module specifiers or use an 8-line import map to make all of our core packages importable (we carefully keep all of our import specifiers compatible with the web and compact import maps). Components are inspectable with regular DevTools. You can even write components right in DevTools, or in a script tag in plain HTML.
I honestly wish this weren't a unique selling point of Lit, but as far as I can tell, of the "major" frameworks or component libraries out there (including React, Vue, Angular, Solid, Ember, Svelte, Stencil, Preact, and more) we're the only ones that fully work with plain JS within our regular mainstream usage patterns.
We were embedding the component into a legacy application and needed to use a build tool for older browser compatibility but it was a breeze to work with even with the extra complication our build setup added.
Getting back to basics and targeting what most modern browsers support out of the box is a huge plus.
vuejs can work without build system though. it's mentioned in the article, and I used to wwork that way. Eventually i made my own (minimal) build system though using quickjs and esbuild. Precompiling templates and minimising can be useful.
Is there any documentation about using it without a build? All the documentation and tutorials seem to assume a build step. I've found several different "getting started" entrypoints on the site, and they all begin with "npm install" and "import {LitElement, html} from 'lit';" which won't work without a build step, correct?
Regardless, we do have some workflows for completely "tool-less" (or local-tool-less).
- We publish bundles to jsDelivr: https://lit.dev/docs/getting-started/#use-bundles
- You can import directly from JS module CDNs like unpkg.com, ie:
Ultimately we're not really dogmatic about things and support a wide variety of tools. The thing we care about is that tool-less development is possible, and that you can use standard semantics and tools with no special configuration (ie, regular Node resolution is all that's required locally, we stick to standard ES2020).>I generally don't consider installing from npm a build step. I also don't really consider rewriting import specifiers a build step - it's just locating files on disk.
Sorry, maybe this is just my JS ignorance, but I don't understand how this runs without a build step.
If I run npm -i lit, it puts lit into node_modules. But then if I put "import {LitElement, html} from 'lit';" into a JS file and run a dumb static webserver (like python3 -m http.server), what's linking "from 'lit'" to the necessary files in node_modules?
I just tried running the first tutorial[0] verbatim after npm installing lit to the same directory, and I get:
I'd love to use this, I'm just not following how this works.Is it designed to run server-side rather than for static sites?
[0] https://lit.dev/tutorials/intro-to-lit/
More info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sc...
It seems like a handy way to make my code match the code in documentation that assumes a Node environment.
https://github.com/guybedford/es-module-shims
https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-a...
1. Use a server that will use the node module resolution algorithm and transforms it to a relative specifier as it encounters the import (transform/build-ish?)
2. Use experimental "import maps" to go fully buildless to tell the browser where to point that bare module to (buildless)
3. Bundle all your code (build step)
4. Use a bundle from a CDN
Here are some examples of each:
Server that transforms import specifiers (lit.dev does this by default):
https://lit.dev/playground/#gist=9092862a77acf73dd1e6faa73a3... (build-ish)
Import Maps (2):
https://lit.dev/playground/#gist=0c4f66396b333c82cb27e7dd3b2... (buildless)
https://lit.dev/playground/#gist=91c0b286698612b013eb81881ff... (build-ish)
Url Specifiers (2):
https://lit.dev/playground/#gist=05655736300f0425644e61a6264... (buildless)
https://lit.dev/playground/#gist=6d23e8e8dadacd7488bdec81d0f... (build-ish)
hope this helps with some of your questions
It does have lower level packages if you don't want ready made components too
[0]: https://www.fast.design/
FAST is kind of like a Lit-clone (though it patches global prototypes) plus a design system base.
There are lots of design systems built with Lit that you can chose from instead of us including our own: Shoelace, Adobe Spectrum, Redhat Patternfly, IBM Carbon, Material Web Components and more.
One thing I struggle with from time to time is that if I have a web component created in lit, and somewhere inside I have a dependency that has some CSS, that CSS doesn’t seem to work when the web component is consumed by JS.
The only workaround I found was to override the behaviour and instead of returning a shadow dom, return the component on its own, but that has its own downsides.
Jealous! All I ever see are react shops, angular shops that are now 'legacy', and the odd place that uses vue.
Lit is another library that is made up of a couple of things one of which is lit-html but is closer to a React or Vue by way of comparison.
Some noticeable differences between it and other view layer libraries include the fact that it was written by a team who are arguably much closer to actual browsers engineers and it uses native Web Components. The side-effect of this is that it’s incredibly fast and lightweight. I tend to think of it as the 5kb developer experience enhancement that takes Web Components as a primitive and makes them an actual pleasure to work with.
There are also a series of enhancement packages that are also totally opt in if you want more out of the box like internationalisation, client side routing etc.
If you go to lit.dev the documentation is actually pretty great and it’s probably the easiest way to get started.
But thank you for your response. Did not know that I was looking for Lit all these time. Every time I want to dabble into web dev, I get frustrated by multitude of tools need to setup first. All I wanted was to import a library using script tag and start building.
Vue (v2, don't know about v3) can also work without a build script, either with a render function, or a "template" (an string that mixes HTML with expressions).
We started using it because we knew it could be embedded into any other framework, we are using two others in the company. But are starting to use it for SPA also. Glad to see it growing quickly.
Thinking about possibly reworking our SPA to use Phoenix LiveView most of the abstractions are there already.
That's the main reason for me not to use a build system. You lose that "transparency" and accessibility of the code. With "raw" HTML/JS, the user can just copy paste your HTML/JS (often it was just 1 file for both combined!) into notepad, save as HTML and have their own website/"app"!
At least, that's how I felt until I got comfortable with TypeScript, and now I will refuse to use raw JS even for small projects... Alas! (Would be nice if V8 could strip type annotations and just run the plain JS hidden underneath... would also help with pasting TS snippets into the dev console!)
Instead of the increasingly silly attempts of coming up with weirder ways of doing server side rendering.
[0] https://reactjs.org/docs/add-react-to-a-website.html#quickly...
Some things can’t be done like this, like TypeScript, for which I keep it to a minimum, look for stability, and often include a full copy in revision control.