曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
Web前端

Frontend Heresy: No Vite, No Webpack — Dragging React Back to the HTML Era as an Anti-Engineering Practice

Anti-engineering React practice? Writing UIs by including React directly in HTML? That's heretical enough. Toolchain? HMR? Don't need them — load dependencies by hand, control the browser cache yourself. Why bring in a whole engineering stack for a simple interactive page? This trade-off is pretty “aloof”.

曦远 (DealiAxy)约 20 分钟
目录10 个章节

Preface

I've been busy with renovations and developing new products lately, so I haven't had time to update this blog. The consequence of rapid iteration is that the architecture hasn't kept pace with feature growth, and I now have a faint sense that things are slipping out of control. Over the past few days I've also slowed down the pace to think through the overall plan.

I'm also taking the chance to organize my notes — without putting anything out I feel uneasy 🤣

Alright, back to the topic. Why is the title “Frontend Heresy”? In today's highly engineered frontend world, tools keep proliferating, and everyone is saying stop updating, I can't keep learning. I'm going the opposite way:

  • Don't use Vite
  • Don't use Webpack
  • Don't run a dev server
  • Don't do HMR
  • Don't maintain node_modules

Drop React straight into an HTML file and write the UI!

This sounds a lot like still using Dreamweaver to write web pages in 2025, hahaha 🤣

This article records an anti-engineering development practice that can be called “frontend heresy”.

Phase 1: Runtime JSX

At first, I used the most primitive, and most “heretical”, way of using React.

<script src="react.development.js"></script>
<script src="react-dom.development.js"></script>
<script src="babel.min.js"></script>

<script type="text/babel">
  function App() {
    return <h1>Hello World</h1>;
  }
</script>

The characteristics are obvious:

  • No Node needed
  • No npm / pnpm needed
  • Open the browser and you can write React
  • JSX is compiled in the browser in real time by babel-standalone

I have to say, this approach is actually pretty satisfying to write with (flees

  • Extremely low cognitive load
  • No toolchain anxiety
  • HTML is the entry point, what you see is what you get

But the downsides are equally obvious:

  • Dependencies are huge, easily several MB
  • JSX runtime compilation is extremely slow
  • Once the page gets complex, load time visibly increases

Even this simple app of mine took half a minute to load locally 🤣

In fact, this usage is React's official early demo + teaching-level usage, later explicitly marked by the official docs as: Not recommended for production

I've also written an article about this before: Including React and JSX in HTML

Practice also proved that this approach can only be used for local demo testing — it simply cannot be shipped as production.

Phase 2

gulp + babel, I only want JSX compilation

I chose a tool that looks very “retro” today: gulp.

This tool is simple, but very useful. I've always liked it a lot, and you can see gulp's footprint in many of my projects:

The usage looks like this:

Explicitly add the jsx components to the processing list, in order

const jsxComponents = [
  "Alert.jsx",
  "Toast.jsx",
  "Login.jsx",
  "Register.jsx",
  "ChatView.jsx",
  "DetailView.jsx",
  "App.jsx"
];

I gave up automatic dependency analysis, which in modern frontend is almost “original sin”. 😂

But in a project with clear boundaries and a controllable scale, it is actually the most controllable approach.

gulp does a few simple things here:

  • JSX → JS
  • multiple files → one bundle
  • minify
gulp.task("concat:jsx", () => {
    return gulp.src(jsxComponents, { base: "." })
        .pipe(babel({
        presets: ["@babel/preset-env", "@babel/preset-react"]
    }))
        .pipe(concat("dist/js/app.bundle.js"))
        .pipe(terser()) // Use terser for minification
        .pipe(gulp.dest(paths.root));
});

No loaders, no plugin hell, no configs fighting each other.

The final artifact is just one file: dist/js/app.bundle.js

The Final Form

After the build, the entire application's entry point is an extremely plain HTML file.

<script src="lib/react/react.production.min.js"></script>
<script src="lib/react-dom/react-dom.production.min.js"></script>
<script src="lib/axios/axios.min.js"></script>
<script src="dist/js/app.bundle.js"></script>

A few things I really like:

  • All dependencies are explicit

  • React doesn't need JSX to run

  • Browser cache is under my own control

    <script>
        (function () {
            const scripts = [
                "dist/js/app.bundle.js",
            ];
            const v = new Date().getTime();
            scripts.forEach(src => {
                document.write(`<script src="${src}?v=${v}"><\/script>`);
            });
        })();
    </script>
    

Why?

The reason for doing this is actually simple: my backend needed a simple interactive page, and I neither wanted to create a new Next.js/Vite frontend project for it, nor did I want to use backend template rendering + HTMX/Alpine.js.

  • The former's problem is cost:

    For a page with very clear functional boundaries, bringing in an entire frontend engineering stack means extra maintenance of a project structure, a set of build configs, and a whole toolchain lifecycle. That cost, for this requirement, is excessive.

  • The latter's problem is expressive power:

    Backend template rendering with HTMX or Alpine.js is indeed lightweight, but when the page starts to have more complex state transitions, component reuse, and logic composition, I quickly start “manually simulating a component system”. Rather than twisting around in template syntax and directives, I'd rather just use a component library I'm already very familiar with and whose mental model is stable enough (the React ecosystem).

So the question became:

Can I use only React's “expressive power”, without bringing in the entire engineering system behind it?

This is exactly the starting point of this “frontend heresy” approach.

Value

What does this “frontend heresy” approach solve?

In one sentence: how to get a sufficiently comfortable interaction layer without introducing extra engineering complexity.

Specifically, this approach achieved a few things:

  • No need to maintain a separate frontend project
  • No need for a dev server, HMR, or complex build configs
  • No need to bear the long-term cost of a full frontend engineering stack for a small page
  • But you can still:
    • Use component-oriented thinking
    • Manage clear state and interaction logic
    • Write structurally clear, maintainable UI code

This isn't about chasing the lightest possible stack, but about chasing: a match between complexity and the scale of the requirement.

When the requirement is simple enough, engineering itself should be something that can be dismantled, restrained, or even refused.

Trade-offs

👍 What I gained

  • Extremely low cognitive load
  • An extremely stable build process
  • No runtime build dependencies
  • Very suitable for:
    • Admin systems
    • AI consoles
    • Internal tools
    • WebView / embedded pages

❌ What I actively gave up

  • HMR
  • Automatic code splitting
  • Full TypeScript pipeline
  • The dividends of the ecosystem plugin landscape

Wrap-up

What I did is actually simple: peel away complexity layer by layer, until only the layer the business actually needs remains.

I don't recommend everyone do this, but I'll keep making some anti-engineering attempts. Going against the grain in the AI era is actually pretty interesting 😄

_EOF_

Frontend Heresy: No Vite, No Webpack — Dragging React Back to the HTML Era as an Anti-Engineering Practice

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…