Hacker News new | past | comments | ask | show | jobs | submit login
Writing JavaScript without a build system (jvns.ca)
334 points by panic on Feb 16, 2023 | hide | past | favorite | 177 comments

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 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).

- 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:

    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).

Ah, gotcha. Thanks!

>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?

[0] https://lit.dev/tutorials/intro-to-lit/

When one writes `import 'lit'` the browser doesn't know where Lit is. This is called a bare module specifier vs an absolute import specifier:

  import '/home/user/project/node_modules/lit/index.js'
A relative specifier:

  import '../node_modules/lit/index.js'
Or a URL Specifier:

  import 'https://unpkg.com/lit?module'
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):

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

Cool, appreciate the thorough answer! I'll give Lit another look now that I understand how to run it buildless + static.

Looks like you need a <script type="importmap"> -- it could map 'lit' to the CDN hosted version or to a relative path.

More info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sc...

Ah, thanks! I'd never encountered these before.

It seems like a handy way to make my code match the code in documentation that assumes a Node environment.

No problem! There's also a polyfill if you need to support Safari:


Thankfully iOS 16.4 will be natively supporting import maps so fingers crossed this might be easier soon


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.

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.

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!

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

[0]: https://www.fast.design/

FAST works without tools as well: https://www.fast.design/docs/fast-element/defining-elements

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.

What is design system? I have little to no web dev experience. Is it a set of cohesive custom UI components?

Indeed yes!

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.

First of all, thank you for the work you have done on this, I love lit, use it all the time at work.

Jealous! All I ever see are react shops, angular shops that are now 'legacy', and the odd place that uses vue.

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 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.

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.

> ... 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).

Correct. It's how I use it. I think that it no longer applies with V3.

It still does (according to their docs - sorry for no link now but they have a guide on how you can use vue for different use cases)

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.

How to work around decorator usage, though? The examples in the documentation seem to be built around the assumption of a build step being present.

There is a JavaScript/TypeScript toggle on all the code samples, just click that and you’re all set.

Yeah that's a really good point, actually.

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.

Can you give me ELI5 difference between Lit and Lit-html for a non-web dev?

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.

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:

      "compilerOptions": {
        "checkJs": true,
        "target": "es2020"

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.

Firefox was built with web components? Whaaaat?

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.

There are component frameworks that don't require a build step.

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.

[0]: https://git-scm.com/book/en/v2/Git-Tools-Submodules

[1]: https://www.jsdelivr.com/

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).

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.

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).


    "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.

[0]: https://docs.npmjs.com/cli/v9/configuring-npm/package-json?v...

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.

[0]: https://volta.sh/

Wow, Volta does look like exactly what I've been looking for.

npm can't install the correct version of Node to use automatically, but nvm can using "nvm i"

If you use a `.node-version` file it will work with both nodenv and asdf

nvm is slow when changing directories, fnm is a fast drop-in replacement

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.

[0]: https://github.com/volta-cli/volta

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!)

What about source maps?

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.

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.

> 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.

[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

+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].

[0] https://lit.dev/docs/getting-started/#use-bundles

[1] https://lit.dev/docs/templates/overview/

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.

Hopefully, the "Type Syntax in JavaScript" proposal is accepted one day, and you don't have to transpile ts => js anymore.

Use npm to install libraries one time only.

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.

JavaScript and CSS are to Stoicism as Typescript and SASS are to _____?


It should be “JS and CSS are to TypeScript and Sass as Stoicism is to _____?”

saint augustine?

Hedonistic excess.

An offended stoic.

> npm > Try to use as few dependencies as possible.

Hahaha. You joke.

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...

Great she mentioned https://unpkg.com/ which is a great place to start.

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.

Side note: How on earth did I not know about the cute little websites she makes? They are amazing!




I agree with calling out esbuild as a great, minimal, stable tool.

I recently had the chance to start a small greenfield project. As we have a lot of "tribal knowledge" of Vue I decided to keep it, but go with a "minimum viable tooling" approach. This means esbuild with no plugins, .tsx instead of .vue files (because esbuild natively supports TSX), no hot module reloading (just esbuild's simple refresh-the-page-on-build), and absolutely no CSS-in-JS, just a single style.css using BEM conventions.

The same approach would probably work with any other frontend framework that can use JSX or plain JS files, even if you do run it through esbuild before it gets to the browser.

In my experience, most of the complexity that creeps into JS projects is fancy build tools. Using fewer, better tools which focus on standards-compliant content is the way forward.

Hey, congrats. I have a similar setup, except using react.

And I'm torn about styles. I've inlined some and done plain css for others. Though it's still just plain tsx to esbuild.

I've loved it. You can add timestamps to log output by piping a command through the ts Linux command. And I have never seen the second change (it doesn't do sub second timestamps) between the start of a rebuild and the rebuild being done. It's amazing.

I make a lot of small simple websites, I have approximately 0 maintenance energy for any of them, and I change them very infrequently.

I do this too, and no-build is definitely the way to go for them.

To make this even less effort, I log directly into the web server and edit them live. Once I'm happy I check in the changes.

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.

i design all my javascript libraries to not require build systems

you can just include them in a page an go:





For simple Typescript without node:

1. Typescript in VSCode.

2. Simple script to watch a project directory for changes to .ts files that runs...

3. ...'deno cache <file>' to transpile them to JS. Then copy them from the cache directory.

deno also has a bunch of options for formatting, bundling etc.

On personal projects that is my approach, vintage Web development, just like I was doing 20+ years ago.

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.

Already being proposed! Would be really cool https://devblogs.microsoft.com/typescript/a-proposal-for-typ...

Python typing kind of went in that direction.

I can happily live without TypeScript (ka-chow) but at the same time I don’t see why not.

Vue.js seems to be the natural choice for progressive JavaScript Apps without a build system.

We've also removed npm build systems from our newest .NET Project templates which uses JavaScript, Import Modules to progressively enhance Razor Pages Apps, which I've also written about in:

"Simple, Modern JavaScript"


As you have access to full Vue 3 you're still highly productive being able to use Vue 3's Composition API and Plugins & Components which are able to be loaded directly in the browser.

We're still using TypeScript definitions for enabling static analysis benefits for external libraries but have switched to JSDoc type annotations for App code to avoid any runtime transpilation.

That node SSL issue is such a doozy.

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.

With JS having `import` these days, there really is zero point in bundling if you're writing normal modern JS. Just let the browser deal with caching, it's good at that, it's literally half of what it's been optimized for. The other half being JS execution.

Is JS import similar to Python import?

They're completely different programming languages, so: that question's nowhere near specific enough? Similar in what way? (how imports statements map to on disk files, how dependency resolution works, how caching works, etc. etc; There are too many aspects to imports, making it impossible to answer your question as posed)

That partly answers my question. Thank you.

I might be one of the few users here who looked at the title and thought "isn't that usually true?"

I don't need to write JS much, but when I do, it's with a browser open in one window and a text editor in another.

I resonate with this topic. Checking your own repos on a new computer is one thing… inheriting someone else’s project and running it on your machine in the node ecosystem is very rough.

Anyways, I made a slightly more advanced buildless vue project here: https://github.com/kyleparisi/buildless-vuejs

It has the advantage of using .vue files which I enjoy. Oh and guess what… it has code splitting because you have to define what components the page needs ;).

I'm in the same camp and never want to run a build process for JavaScript ever again. I've been slowly improving https://dlitejs.com/ which is a minimal (5kB minified + gzipped) JavaScript framework which works great loaded from unpkg. It's pretty crazy to see how lean a framework can be nowadays (even with two-way binding, directives, and event binding) when leveraging newer standards like Web Components and the Shadow DOM.

Web Components suck, Custom Elements is where it’s at ;)

Just leave out the attachShadow.

> I’d love more tips for no-build-system javascript

Me too. And the tips already in here are pretty good!

While I think the goal of being able to write JS without a build system is fine, I'd still never skip setting up a build system. Static analysis, formatting, linting, optimizing, testing, and other such parts are way to valuable to ever give up. The ability to run in a dev environment without a build is valuable from a performance and feedback loop standpoint, but at the end of the day, the tooling needs to be there.

I lint using Deno, single binary, no need to pollute the system with anything more than that.

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.

EDIT: Looks like there's a proposal: https://github.com/tc39/proposal-type-annotations

IIRC that's how the Flow type annotations worked, but of course it lost out to TypeScript.

Correct, but to be more precise, both Typescript and Flow allow for both JSDoc and the more common TS-like syntax.

The problem with build systems isn't the existence of a build step but the necessity of the build system and its configuration.

Typescript is baffling-ly complex to use compared to something like python.

> tsc build

What's complex about that?

To be fair, tsconfig.json has a ton of options, and you often can’t just use the defaults. It’s necessarily complex, unfortunately.

python runtime is great. bites back with the dependency versioning though

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

Use Vite and don't look back. It's so refreshing compared to Webpack.

You’re missing the entire point. That’s still a build process and still has additional overhead for both development and deployment.

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.

In this case the A in YAGNI stands for “Are”.

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'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.

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.

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.

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.

[0] https://reactjs.org/docs/add-react-to-a-website.html#quickly...

My goto build system is:

- make

- Google's closure compiler (standalone Jar)

- imagemagick

It builds fast and never goes out of date.

It is really great to finally see no build web development gaining traction. Ever since the death of IE there has been an opportunity for dramatic simplification of the web dev stack that is unserved by the major frameworks.

I did a talk about the topic of no build web apps recently. The slides are here for anyone interested: https://1drv.ms/p/s!ArEoTVF2ayv3lJg4oECZk2h5eN3I3Q

In preparation for that talk I’ve also adapted create react app’s default project into a no build template, including a variant with routing. That can be found in github: https://github.com/jsebrech/create-react-app-zero

I just simply do not use anything for my single page web apps that requires build. I still use build on production but strictly for bundling / minification. So far no problems.

Also I do not really use any frameworks. Just some libraries with particular functionality when needed.

You can use ES6 imports for JS and CSS directly in the browser, by including es-module-shims[1]. No need for a package manager, transpiler, or build step.

<script type="module">

import Pkg from 'https://site.com/pkg.js'

import sheet from 'https://site.com/sheet.css' assert { type: 'css' };


1: https://github.com/guybedford/es-module-shims

So much this. I using Preact and still have to manually fish out files from build folders in Git repos just to have a usable library to import directly, because somehow “npm install” became the status quo.

Same situation here, but Mithril.js instead of Preact.

For small personal projects I just use git submodules[1] and symlinks, and I still haven't regretted it.

Maybe it's because I set `submodule.recurse true` and `push.recurseSubmodules check`, but I still haven't found the reason why they get so much hate. I started using them (only on personal projects) partly to find out by myself what all that was about.

For $DAYJOB I obviously go with what's mainstream and "best practices", but on personal projects I try to experiment and challenge assumptions to see how much I can simplify stuff, and what decisions I come to regret 6 months down the line.

I either learn that some things are not as bad as others say; or I learn more about the specific pains caused by not following some advice (so I'll have more context on when it shouldn't be applied).

[1]: Pointing to my own mirrors. Because not owning your dependencies is a recipe for disaster.

I made a build-less preact template once, and still use it. It just works %)

My strategy for dealing with bit-rotting builds is to use yarn with plug-n-play. It goes farther than creating a lockfile by saving a zip of every dependency (and yarn itself) in a directory that you check into your repo. Combined with a Node version file, you can be pretty sure that you’ll be executing the exact same code in a decade.

The trade off is that because it’s actually reading JS from the zip files at runtime, a lot of Node packages don’t play nicely with it. Once you get things set up, it’s great, but getting there can be exhausting for a hobby project.

> plug-n-play [...] goes farther than creating a lockfile by saving a zip of every dependency (and yarn itself) in a directory that you check into your repo

Imagine that: using your VCS/SCM to version control the code that goes into making your app work.

> Combined with a Node version file, you can be pretty sure that you’ll be executing the exact same code in a decade.

I have news for you: the Yarn-/NodeJS-specific stuff isn't necessary. With the code in your repo, you're able to roll back to a specific revision at any time. That's the whole point of version control.

Keeping a note of which version of NodeJS the project was originally developed with is absolutely necessary; it periodically makes breaking changes in major releases. I literally just had to debug a project where the build was failing due to a Node version difference.

And you _can_ manage a dependency tree and keep everything up to date by hand, but it's much nicer to let Yarn do it for you.

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.

[0] https://tailwindcss.com/docs/installation/play-cdn

I'm working on a bigger long, term project, but I've been aiming for the sweetspot of effective yet fast builds. She noted esbuild for its stability, but it's also quite fast.

My setup for a nice ui dev experience now consists of 3 things:

- yarn plug-n-play

- esbuild, invoked from thr cli and not node, with no plugins. I run it in watch mode.

- caddy as the reverse proxy to serve the watch files and the api under a single url

It has been refreshing fast. And add into that generating TS api bindings for my backend server automatically... well it's great.

i found esbuild to be inadequate last time i tried it. which i think was a couple months ago. doesn't support ts libraries nicely. still need to output to both mjs and cjs and output d.ts files for proper compatibility. only thing that fits the bill is rollup

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.

Having been away from the JS world for ~5 years, it has been an eye-opener to come back to ES 2022 + VS Code + JSDoc + //@ts-check. I'm writing an application in one big HTML file with one big script tag and no build system, and actually kind of liking it. We'll see if it scales as far as I need it to, but for now I have something with decent DX and minimal bit-rot risk.

The fact that ES2015 was not backward compatible opened the Pandora's box to the plethora of build-system-only libs. Now the browser and your application are too distant. Exposes the dev to a world of tooling but increases friction on building, points of failure and complexity. Recently we are trying to fit them again and this post is a useful guide to do it

The real question is why do we need React, Webpack, Tailwind, sass, jsx, etc.

I've been developing web apps for the last few years and I really can't understand why all these are needed. They do not make my life easier as a developer, nor they make the code more readable.

I've asked around why do we need, for example, React, and never gotten a straight answer.

How do you keep changes between the DOM and JS in sync without a framework? This is, from my understanding, the main value they provide.

By adding event listeners to the DOM elements that may receive updates?

I don't see why you _have to_ use a framework for that. Have people forgotten to write simple code using basic JavaScript?

I wonder if pinning dependency versions using something like asdf's .tool-versions would help. Then you don't have to worry about node upgrades. Even simpler, you could check the esbuild binary into git.

>I’m not totally sure why some libraries don’t provide a no-build-system version

Yeah, Ive wondered that as well. Whenever I see I have to setup a complicated build system just to run a simple hello world example, I just go yuck and stop

Also _just_ did something like this and it's really rewarding: https://evertpot.com/node-changelog-cli-tool/

No build system was ever used in the making of Pinterest's widgets:


Deno solves all of this, too bad they don't support Solid.js.

Deno has kinda solved it for the server side, but not for the client side.

If this was important to me, I'd just use jQuery, though HTMX also looks like it'd work well (just haven't used it yet, so can't speak with authority).

I wonder how hard it would be for Browsers to natively strip TypeScript syntax from the .ts files without checking and run them as .js

So no compile time checks and no runtime checks? What would the advantage of types be?

With decent dev tooling, you are getting edit-time checks equivalent to compile-time checks (the only difference is that there is no compilation after the checking), so there’s no loss. The advantage of compile-time checking has nothing to do with “compile time” except that compile time is inherently before runtime, and edit time is even earlier.

The fact that you can't run without passing the checks is an advantage to compile time checks in some particular (social) contexts, although a disadvantage in others.

I use : - jquery - vanilla JS - Vue

wait.. Tailwind is a big CSS file?

Tailwind is a too-big CSS file. If you want to use Tailwind, you must use a builder. This guy is clueless.

   wget https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css
  HTTP request sent, awaiting response... 200 OK
  Length: unspecified [text/css]
  Saving to: ‘tailwind.min.css’

  tailwind.min.css               [                       <=>            ]   2.80M   321KB/s    in 10s     

  2023-02-17 18:44:58 (286 KB/s) - ‘tailwind.min.css’ saved [2934019]

In short: 2.8 megabytes, 10 seconds for the download.

From the post:

> but Tailwind 3 doesn’t seem to be available as a big CSS file at all anymore […] so I’m going to keep using Tailwind 2 for now

Gee I wonder why. The length people go to make internet worse for everyone…

Tachyons is a similar utility class framework, and a lot smaller at somewhere under 20kb, IIRC.


looks like HN demand might have brought the site down :P

This site can’t be reached

> 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.

If you put it in docker there's basically a decade guarantee

Not if you want to allow developers to continue using the latest JS patterns, frameworks, libraries, etc.

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.

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.

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.

And a Tcl "starkit" aaages ago.

Huh… in Python I feel like the tooling is largely missing.

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.

python is rarely streamed across HTTP, I believe that's the reason js is treeshaked.

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.

Guido is the one who picked Python's name. Wikipedia actually has an entire article on the history of Python:


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.

> 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.

> 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.

Precisely, because there is generally little need for it.

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.

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.

Why is this opinion resulting in negative points? It speaks to the sentiment of the post. Happy to discuss the topic constructively.

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.

Speaking of excluded batteries: Python. Most hardware doesn't ship with it of out the box.

Be mindful of your blindspots.

Python has a fat standard library with a ton of cruft, but they are also missing crucial stuff like a `fetch`.

Just use Vue. It's perfect for this use case, has a ton of help online if you need and plenty of paths to take if you want to upgrade your project in any direction. Seriously way reinvent the wheel here?

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact