laitimes

Is it possible to develop web applications without any framework?

Planning | Cai Fangfang

Author | Jér me Beau

Translated by | Knowing the mountain

Is not using a frame equivalent to building wheels repeatedly?

This article is authorized by the original author, translated and shared by InfoQ, please indicate the author, translator information and source.

What used to be popular was Angular, then React, now Vue.js... Others like Ember, Backbone, or Knockout are all but disappearing. Some standards, such as Web Components, are rarely used. It seems that some new frameworks are released every year, such as Svelte and Aurelia, and each framework has a corresponding object on the server side (NestJS, NextJS or Nuxt for the first ones, Sapper for Svelte, etc.). Non-JavaScript web frameworks such as Django, Spring, Laravel, Rails, etc.) are not to mention. There are even frameworks on top of frameworks (Quasar, SolidJS), frameworks that generate component code for frameworks (Stencil, Mitosis), and NCDP (No-Code Development Platform).

This diversity confuses developers and technology selection decision makers who want to know which technology is worth learning.

Articles comparing these frameworks often appear on the web, as if to help us unravel this confusion. But most authors are usually biased because they may have "used this framework" but "only tried some other framework." Authors with a less degree of bias always come to the conclusion that it depends on the specific situation (depending on performance, tools, support, community, etc.), which is actually a non-conclusive conclusion.

Even if some benchmarks compare different frameworks based on the same application, it is difficult to obtain real results because such benchmarks are limited to the application being tested (such as the to-do application).

Frameworks look like religion (or politics): each one pretends to provide a solution for developers, but each one is different. Each of them claims to offer the best prospects for an app, but there's a lot of debate about which one really lives up to its name. Every framework requires you to follow specific rules, and there may be similarities between them, but it's always hard to transition from one framework to another.

Now, let's look at the "atheistic" approach to frameworks: not using frameworks.

Where to start?

I have over 25 years of professional software development experience, and in addition to that, this article will build on experience building real-world, pure JS web applications (front and back end).

Why not use a framework?

In fact, the idea is still quite new. Back in 2017, Adrian Holovaty, co-founder of Django Web Frameworks, talked about his framework "fatigue" and why he left Django to build his own JS-only project.

One might ask, why would anyone want to develop a web application without using a framework? Why not build on the results that other people have spent years and effort on? Or is it because NIH (Not Invented Here) syndrome causes everyone to want to build a customized framework?

Developers are not more inclined to get into trouble than the average person, in fact, they may be lazier than anyone: they will only want to write less code (so they can make fewer mistakes), want to automate (to avoid human error)...

Is it possible to develop web applications without any framework?

But they also want to be agile, that is, to be able to solve problems easily and quickly.

While "fast" seems to be something the framework promises (scaffolding you and adding reliability), it's not free: they want you to sign a contract, agree to pay a "tax" fee, and put your code into a "lone well" (the term "tax and lone well" comes from Akira, head of the system design team at IBM Carbon Sudhttps://github.com/carbon-design-system/carbon-web-components#readme)。

Framework tax

There is a cost to using the framework:

Follow their API rules so they can provide services to you. This is how frameworks work: your code must follow certain rules, including more or less boilerplate code. What you think about every day is not "how to do this", but "how to make the framework do (or not do) this thing". If you circumvent these constraints, the risk is yours: if you bypass the framework by calling the underlying APIs directly, don't expect them to understand your intent or behave consistently. So, it's a false promise that frameworks will make you "business-focused": in fact, you don't worry less about framework things.

If you want the following things, you have to force an upgrade:

1) Want a new feature (you have to upgrade everything even if you don't need all the features);

2) Want to fix a bug;

3) Don't want to lose framework support (as new versions are released, the versions your application depends on will be deprecated).

If the framework has a bug but doesn't have a clear planned fix date, it can be very frustrating (and possibly put the project at risk). Framework libraries (such as widgets) or plugins provided by third parties are no exception, and if you keep using older versions, their compatibility with your application will get worse and worse. Maintaining backward compatibility has become a very cumbersome thing for framework maintainers. They found that tools for developing automatic code upgrades (Angular's ng-update, React's native upgrade assistant, Facebook's jscodesshift, etc.) would be more profitable.

You need to learn how to use them (what they can or can't do, their concepts, APIs, ecosystems, tools), including understanding what changes may occur in the new version. This may be easier if you choose the most popular framework at the moment, but it's impossible to understand every aspect of a framework. And the hype never stops: if you decide to use another framework in a new application (or worse, migrate from one framework to another), then all your investment in the old framework will go to zero. This is why many enterprise projects lack dynamism, even though each project may be different from the previous one. The late David Wheeler once said, "Maintaining compatibility means deliberately repeating someone else's mistakes."

Delegating control to the framework is a compromise on the framework's flaws: you may not be able to do anything you want (or prevent the framework from doing what you don't want them to do) or you may not be able to get the performance you want (because of the extra layering, ubiquity, larger code volume, or backward compatibility requirements).

Skills are fragmented. A lot of developers either don't know much about the underlying APIs (because they always use what the framework provides) or live in the past (only know outdated knowledge and don't know the latest improvements and features). "Tool laws" often lead to over-design, building complex solutions to simple problems, and the knowledge of building simple solutions gradually becomes fragmented. Guided by the guide, we lose (or do not gain) a good culture of software design (principles, patterns) and lose (or do not gain) experience in building important projects. Just as users of CSS frameworks (Bootstrap, Tailwind, etc.) lack CSS skills, users of web frameworks are doomed to lack modern web API and software design experience.

Is it possible to develop web applications without any framework?

Once you put the money into the frame, it's hard to get it out.

Frame lone well

In addition to having to pay a "tax" fee to reap the benefits of the framework, they pose another problem if the framework is not standardized.

Because they enforce that you follow the rules of the framework — and each rule is different — it means that your application will be tied to a proprietary ecosystem that locks your application code with proprietary APIs (and its upgrade process). This is a risky bet for your project, as they suggest:

No portability: migrating code to another framework (or a new version with significant changes, or even not using a framework) will be very expensive, including the cost of what may be needed for retraining;

Your code is not interoperable with other framework runtimes or other framework component libraries that you want to use: most frameworks have difficulty interoperating with each other because of the different rules.

Of course, at the beginning of the project, you can choose the most popular framework. This may be acceptable for a short-term project, but not for a long-term project.

Is it possible to develop web applications without any framework?

Frames come and go. Starting in 2018, 1 to 3 new frameworks have replaced the old ones every year.

However, there is no such thing as a single well in the standard framework. On a web platform (i.e., a browser framework), using standard web APIs can reduce your investment risk because they run on most browsers. Even if not all browsers support it, it can still be compensated for by polyfill.

For example, today's Web components are both portable (available in almost all browsers) and interoperable (can be used by any code, including proprietary frameworks) because they can be encapsulated into arbitrary HTML elements. Not only do they have better performance, their runtimes (custom elements, shadowed DOM, HTML templates) also run as part of the browser, so they're already there (no download required) and are native.

Is it possible to develop web applications without any framework?

Very few developers try to escape the framework silos.

So is the framework inherently bad?

If you're creating your own framework for implementing application logic, you can't say that frameworks are bad: any application needs to implement its own business rules.

The framework is good if:

Application-specific: any application will eventually design its own "business" framework.

To become a standard, for example, a web platform is a standard web framework, and the components that web component frameworks (lit, stencil, skatejs, etc.) ultimately build conform to this standard.

Add unique value that is missing from some other solutions, including other frameworks. In this case, you have little choice, and these added values justify the implied cost of lock-in. For example, an operating system-specific framework follows the standards of the operating system, and there is no other way to get an application or extension that meets the needs.

Used to build non-critical (short-term, low-quality expectations, and acceptable "taxes" and "silos") applications. For example, use Bootstrap to build prototypes, MVPs, or in-house tools.

Defragmented goals

Simply put, the goal of avoiding using frameworks to build applications is to:

Maximize flexibility by avoiding the "one-size-fits-all" constraints of the framework. In addition, the constraints of the rules are removed to enhance the creativity of the application. Most Web applications developed using Bootstrap fall into this category because they have a hard time getting rid of predefined components and styles, and will ultimately have a hard time thinking about things from other perspectives.

Minimize reliance on over-hyped frameworks. Not being locked into the framework avoids portability and interoperability issues.

Maximize performance by doing only the finest granular operations when needed (for example, not relying on the framework's refresh cycle) and reducing dependencies to use only some of the necessary lightweight libraries.

Of course, our goal cannot be to "reinvent the wheel." Let's see what to do.

Choices outside the framework

So, how do you develop an application without a framework?

First, we must make an anti-goal clear: don't confuse "building applications without frameworks" with "superseding frameworks." Frameworks are a general-purpose technical solution for hosting arbitrary applications, so their goal is not your application, but all applications. Instead, going out of the frame has the potential to allow you to focus more on your application.

Is it possible to develop web applications without any framework?

Not developing an application using a framework does not mean that you want to reimplement the framework.

To assess the difficulty of building an application without using a framework, let's understand that it's not as difficult as building a framework because these are not our goals:

Build proprietary component models (containers that implement specific component lifecycles);

Build proprietary plugins or extension systems;

Build a fancy template syntax (JSX, Angular HTML, etc.);

Implement common optimizations (change detection, virtual DOM);

Framework-specific tools (debugging extensions, UI builders, version migration tools).

Therefore, building an ordinary application is not a difficult task of "reinventing the wheel", because this "wheel" is mainly about APIs/contracts, implementations, common engines and related optimizations, debugging capabilities, etc. Abandoning generic goals and focusing on the goals of the application means you can get rid of most of them, and that's the real "focus on your application."

So, how do we design and implement a common application? Because most applications are built using frameworks, it's really hard to devise a way to achieve similar results without these familiar tools. You must:

Change your mind: Don't use framework-specific services. For a normal application, you may not need these services. No change detection is required, just update the DOM...

Use other technical alternatives to perform common tasks that were previously performed using the framework (updating the DOM, lazy loading, and so on).

Some authors, such as Jeremy Likness or Chris Ferdinandi (known as "JS geeks"), have also mentioned this topic. However, by definition, any ordinary application can choose (or not choose to) use one of these technologies, depending on the needs. For example, the authors of MeetSpace only need to use the standard API.

Next, let's take a look at some common "solutions".

standard

Standard APIs are "good frameworks" because they:

Portability: They are available anywhere, and if not, they can be implemented by polyfill.

Interoperable: They can interact with other standards and are used in proprietary code.

Long-standing: Designed by multiple industry players, not just one. They are well designed to be there once they are released, and there is less risk of using them.

In most cases it is immediately available in the browser, avoiding the download process. In some cases, you may need to download polyfill. However, unlike proprietary frameworks,which are destined to become less and less popular, they become more and more usable (gradually reducing the need for downloads).

When choosing a programming language, we need to focus on standards. JavaScript has evolved over the years and now includes features that appear in other programming languages, such as the class keyword and limited type checking support through JSDoc annotations such as @type.

Many programming languages can be compiled into JavaScript: TypeScript, CoffeeScript, Elm, Kotlin, Scala.js, Haxe, Dart, Rust, Flow, etc. They all add different values to your code (type checking, extra abstraction, syntax sugar). Should normal apps appear to use them? To answer this question, let's see if they imply the same drawbacks as frameworks:

Follow the syntax: Most programming languages mandate this (CoffeeScript, Elm, Kotlin, etc.). It's important to note, however, that they're super-sets of JavaScript (TypeScript, Flow), and you can still write some of the parts you choose in pure JavaScript.

If you're using a very old version of a programming language (including JavaScript), you'll need to upgrade, but it's much less frequent than the framework.

Need to learn their syntax. However, you can learn the hyperset programming language step by step because some parts of your code can continue to use traditional JS.

Discretization skills are indeed a risk for non-hyperset programming languages. Because their compilations are universal, they may not be optimal, and you may not realize it. Maybe you can do the same with simpler and more efficient JS code.

Compromises need to be made on the drawbacks, as we can't change the process of translating into JS (or using tsconfig.json for a little customization) or compiling into WebAssembly. Some languages may also overlook some concepts of JS.

Portability because code can usually be translated to ES5 (but sometimes you have to compromise, even if you want to translate to ES6). WebAssembly is new and supported by all modern browsers.

Provides interoperability with other JS code. For example, Typescript can be configured to support JS.

In a normal application, we should be careful to use non-superset languages because they more or less implicitly have some constraints. Superset languages (TypeScript, Flow) minimize these constraints by avoiding "all or nothing", and we should use them where they can bring value.

It's important to note that building a language layer on top of JavaScript means that we add another layer of complexity to our toolchain that can fail for some reason (see below). In addition, after compilation or translation, the benefits of the development phase disappear (type or visibility constraint checks are not usually enforced at run time).

Development libraries

Based on the assumption that the framework is not "rewritten", it is concluded that ordinary JS applications should not use the development library. This is completely wrong. "Reinventing the wheel," that is, rewriting everything from scratch, is not a wise goal. Our goal is to remove the constraints implied in the framework (not the library), and not to confuse it with the dogma of "write everything yourself."

So, if you can't write some code yourself (perhaps because you don't have the time, or because you need too much expertise), there's nothing wrong with using the library. All you have to do is care:

Modularity: If you only need a small number of functions, avoid relying on the entire large development library;

Avoid redundancy: use the development library only in the absence of a standard, and give preference to the development library that implements the standard;

Avoid locking: Instead of using the LIBRARY's APIs directly, wrap them in the application APIs.

It's important not to be fooled by documents or articles that claim that they are not frameworks (because they are "not explicitly defined" as a framework, or without defining a "complete application"): as long as constraints are implied, they are frameworks.

mode

Holovaty says it's not enough to just apply patterns (without using frameworks) to build software.

Patterns are well-known things and are not specific to some kind of development process. They are self-documenting in their own right, as they can be quickly identified by experienced developers.

Here are just a few examples:

Model, View, and Controller Modes (MVC);

Factory mode for creating objects according to configuration;

Observer mode for simplified reactive programming;

Iterator pattern for traversing collections;

Proxy mode for lazy loading, security checking;

A command pattern that encapsulates an action that may be triggered based on the context.

There are many such patterns: you are free to use them to meet your needs. If a pattern provides a typical solution to a typical problem for your application, you should definitely use it. More broadly, anything that conforms to SOLID principles and has good cohesion is good for application flexibility and maintainability.

Update the view

When interviewing developers, when asked what they would primarily worry about when building a normal application, most of them would answer: implement complex model change detection and subsequent "view" updates. This is the typical "tool law" effect, which will make you think about things according to the framework's thinking, but in fact some of your simple requirements do not need to use the framework at all:

A View is simply a DOM element. You can of course abstract them (and you should), but in the end they are just abstract.

Updating them is just a matter of calling viewElement.replaceChild(newContent) and doesn't require updating a larger DOM, redrawing or scrolling. There are several ways to update the DOM, from inserting text to manipulating the actual DOM object, just choose the one that suits you.

In a normal application, "detecting" when a view needs to be updated is often not necessary. Because in most cases, you only know what needs to be updated after an event, and then you can execute the command directly. Of course, in some cases, you may need to make general updates by reversing dependencies and notifying observers (see below).

template

Another feature that developers don't want to miss is writing HTML fragments with dynamic sections or listeners.

First, DOM APIs such as document.createElement ("button") aren't that hard and are actually more powerful than any template language because you have full access to them. Writing long pieces of HTML can be tedious, and if they're really long, you can split them into more fine-grained components.

However, treating these elements as templates does improve readability. So how do you manage them? There are several ways to do this:

HTML templates are now available in browsers (in fact, from 2017 onwards). They provide the ability to build reusable PIECES OF HTML. This is actually part of the web component.

JavaScript has supported template literals since ES6 (2015), and you can easily embed values into strings. You can embed primitive types (numbers, strings, including other HTML code, etc.), but you can't embed more complex elements, such as DOM elements that have a listener registered.

We can embed complex values such as DOM nodes into templates with the help of the markup template literal function. ObservableHQ has designed a very handy tool that you can use to write code like html'$, or implement more complex templates like html'$}.

What should I do with conditional or looping statements in a template? Not to mention that this may never be a good idea (there should be no logic in the UI), you can (and should) implement the logic with JS only, and then use the technique above to insert the results into the template.

event

Now that we have the basic template, how do we bind events to DOM nodes? There are also several options here:

HTML event handler code () can be inserted into HTML source code, but this is not the best approach because the specified handler is only available within the specified range.

The event handler API (button.addEventListener ("click", myClickHandler)) can be used for all nodes created through the DOM API or HTML markup template literal functions.

So what about customization or business events? What if I need to react to some events triggered by a component of my application? There are also a variety of ways to deal with it:

Custom events: You can create your own event classes by extending EventTarget and distribute or listen to them, just like "standard" events.

Theoretically, using EventEmitter is also one way to do it (exists in Node, in the browser as stock), but it is rarely used.

Observer mode: You can build your own observer, or consider using RxJs, which is the standard for this. You just need to build a Subject and notify all subscribers when an event occurs, so that subscribers can react to the event.

subassembly

While developing ordinary applications is different from developing complex infrastructure (i.e., containers for hosting components), it is still a good idea to design them as reusable components (context-independent) if something appears multiple times in the system. Regardless of the technology you use, whether it's a business or a technology, a degree of granular abstraction is still useful: it's always a good idea to encapsulate data and rules related to the same business concept into a reusable object, or to build widgets that can be instantiated in multiple places in an application.

There are many ways to create a component, depending on your needs. Back in 2017, Mev-Rael came up with a number of tricks for handling the state, custom properties, and views of JavaScript components. Of course, we should not be limited to the technology recommended by others, but should first consider our own needs, and then choose the right technology.

In addition to the standard widget components (usually standard Web components), any one component should be able to:

Split the logic and views (MVC patterns are typically used). Mixing them together often results in code that is not easy to maintain and reduces flexibility (for example, if you want to display a record in detail or table form at the same time, your RecordComponent only needs to use DetailRecordView or RowRecordView).

Parameterize the behavior or view of a component.

Notifies the subscriber of certain events that have occurred in the subscriber component in the form of triggering events, typically after user interaction has occurred.

Synchronization: If some events occur, the component should be able to redraw. This can be easily implemented using reactive development libraries such as RxJS.

In any case, regardless of the design strategy you choose, your component (or, more specifically, its associated "view") must be able to provide some HTML rendering results. You can use strings that contain HTML code, but HTMLElement (or Element) is usually a better choice (highly readable, directly updated, can bind an event handler), and has better performance (no parsing required).

In addition, you may want to use external components from third parties. Due to the high popularity of proprietary frameworks, they can make greater use of libraries and groups developed by the community. Most of them are actually not much different from features of pure JS implementations (like JQuery), but the problem is that they lack interoperability, so in the end you'll find that what you need is pure JS or Web components.

Fortunately, such libraries do exist, such as the Vanilla JS Toolkit, although they may be less common. In terms of Web components, webcomponents.org lists more than 2000 elements. There are even ordinary Web components, just less relevant to what we're talking about (more about lightweight implementations than interoperability).

routing

Managing routing in SPA requires the use of the Web History API. While this isn't complicated, you may still want to entrust it to a simple router library like Navigo.

All you have to do is replace one DOM element with another DOM element when routing (using the replaceChildren() or replaceWith() method).

Lazy loading

Loading JavaScript code on demand is a concern for any web application. You don't want to load all your application code in order to display one login interface.

Back in 2009, before the advent of web frameworks, James Burke (a Dojo developer) released RequireJS (originally called "RunJS") to solve this problem. Since then, with the advent of modularity, more technologies have emerged. Starting with ES6 (2015), we can load code dynamically. Yes in Node, but also in the browser:

So how do you split a module into separate files? Packagers, such as Webpack, can do this for you.

Note that you should only use constants in the import path, otherwise the packager will not be able to guess what you want to load and will package all possible files in one file. For example, await import() will package everything into the specified directory because the packager does not know what the variable moduleName will be at runtime.

Native applications

More and more frameworks provide a way for native platforms such as React Native to run, migrate, or compile applications to deploy them as standalone applications on Android or iOS mobile systems.

In addition to considering developing truly native applications, the more common solution is to embed web applications into native containers, such as PhoneGap (which is now decommissioned) or Apache Cordova, and now NativeScript (which supports frameworks like Angular, which also supports common applications), or native web application wrappers like Electron, or Electron Tauri, the lightweight successor.

Server-side rendering

Many frameworks run similar code on the front and back ends, making it easier to implement SEO-friendly server-side rendering (SSR).

This may be a cool and convenient feature, but it's important to note that it can also cause server lockouts. Therefore, before introducing framework locking into your application, you need to consider its impact on the project, infrastructure, client technology, and so on.

Fortunately, you can also implement this feature without using a framework.

Render from the server side

Adopting a common implementation may seem simple at first: Isn't it just returning HTML? Yes, you already have ready-made components, but:

You'll also need a server-side DOM API, because by default, the server-side doesn't provide a DOM API (JSDOM, maintained by Domenic Denicola, or optimized Happy DOM is a good choice).

Your rendering component cannot assume that the DOM is on the client or server side, that is, do not use the global DOM, because on the server side, every request requires a DOM. To do this, you need to select DOM objects (windowdocuments and types, such as Node, HTMLElement, NodeFilter) from the (client or server) application context, rather than getting them directly.

There are several ways to share a render component between a client and server application, such as publishing it in a package repository, but the most flexible should be to have the application package reference modules in monorepo.

Add interactivity

However, once html elements are converted to strings, all event handlers set on those elements are lost. To restore interactivity, you need some "hydration" steps, which are injecting scripts to execute on the client side. Frameworks are difficult to do because of their universality. Shadow DOM, for example, is constantly trying to improve the algorithms in the hope of doing this in the smartest way possible, but it becomes a lot simpler if we narrow the problem down to the application level.

Of course, doing this in a normal server application also means injecting JS scripts into the response message (either by reference or inlining, depending on how "progressive" you want to be, such as embedding the code required by the Web component into the HTML response and having it executed on the client side).

The common solution gives you control where, when, and what is attached: you can send just HTML, load basic interactive JavaScript, load more (depending on the user's actions), and so on.

This is simpler than any of the things mentioned in this article because they are application code, not generic framework code.

internationalization

For many years, internationalization issues were handled through libraries (and eventually integrated into frameworks as well). It's also easy to integrate these libraries yourself, but you can also choose to implement one yourself, because your own implementation can support simpler and more efficient message types than the generic libraries.

It's as simple as this:

Here's what you're looking for:

Type checking: Each message has a static type (and several translation implementations), so the IDE can check if you're using valid message properties and provide you with autocomplete functionality.

Translation integrity check: Cannot be compiled until translations for all languages are available for all message keys.

All you need to do is (load and) instantiate the message classes related to the user's locale. The generic library does not provide this type of business-specific message.

tool

If you want to get rid of the dependency on the strongly constrained software stack, you probably want to get rid of the tooling as well: you don't want to rely only on them (their limitations, performance, bugs, versions) to move forward. You don't want to be bothered by a build problem that you can't solve (or that takes hours or days to fix) (especially if you're using a recently built version that hasn't been fully tested in practice).

That being said, it's still hard to avoid using these tools. In most cases, your product code must be packaged in some way, including downsizing, obfuscation, code splitting, tree roll optimization, lazy loading, including styles, and so on. There's no doubt that existing packaging tools like Webpack, Parcel, ESBuild, or Vite will do better than you.

All you can do is:

Use as few translations as possible. For example, using TypeScript may be a good thing, but it introduces additional complexity, and you must have tools in your toolchain to handle this complexity. Css is the same, especially the latest version, and it's not worth handling them with a preprocessor like Sass.

Use as few tools as possible. The more tools you use, the more likely you are to go wrong or not meet your needs.

If you do need to use tools, choose the most popular ones because they're really tested and are more likely to meet your needs (so you don't get stuck in the "change your needs or change tools"). Using the latest packaging tools too early may save you a few seconds of build time, but it's likely not enough time to understand the tool documentation, handle bugs, or deal with issues caused by lack of support.

The biggest challenge

At the end of the day, the biggest challenge is not technical, but about people:

You have to step out of your comfort zone. Hopefully, you'll eventually understand that using ordinary solutions isn't all that difficult, and that frameworks are far more complex than the benefits they bring. In addition, you may see more new APIs (WebComponents, ES6 modules, proxies, MutationObserver... And the Web is more modern and powerful than you might think.

As for the others, you can try to convince them. They may be reluctant to do so because no one wants to embark on a journey they've never tried before.

Others might tell you:

"You're going to develop your own framework": No, we're going to develop an application, not a framework.

"You're going to write more code": Maybe, but maybe not too much (depending on how many libraries are used), because that needs to be compared to the boilerplate code of the framework. Either way, there will be less code to load.

"You will keep reinventing the wheel": of course not. Frameworks are not used in order not to follow their predefined rules (configuration, lifecycle management, refresh mechanisms, etc.), but we have not forgotten the DRY principle, and we can (and should) use third-party libraries that have been tested in practice.

"You need to write more code for every feature": No, you can follow your own rules instead of using framework boilerplate code.

"No documentation to see": There will certainly be no frame documentation (because there is no frame at all), but you still need to write application documentation. It's worth mentioning that usage patterns help you automate documentation of your software design. You only need to care about the code documentation of the application, and if you use one more framework, you need to look at more than one document.

"There won't be constraints or patterns to guide developers": No, if you do need constraints, nothing can stop you (you just need to define the contract).

"You're missing out on performance gains," such as the once hyped virtual Dom (now being challenged, including from the Svelte or Aurelia frameworks): No, because it's the framework itself (for commonality) that needs these "performance boosts," not the application. Conversely, generic frameworks are more likely to miss some of the performance gains that can be achieved with custom code.

You run into this problem because you're not using a framework. Every problem (including vulnerabilities, delays, recruitment, etc.) is blamed for not using a framework. Because the experience of most developers is that everything that works properly uses frameworks, by default, not using them will be considered risky. Once a problem arises, this assumption will be considered correct, whether or not it is related to not using the framework. They forget that they will encounter similar problems when using frameworks.

"We can't find a developer": They'll say it's hard to find a developer who can write pure JS code. This statement is right and wrong. Because many developers (not to mention managers) will find themselves more accustomed to using frameworks. If they have never used or understood basic web APIs, they may be afraid to build a web application from scratch. However, if you want to develop high-quality applications, you shouldn't go to this type of developer. Of course, it's easy to find a React developer right now, but you need more than just a React developer, but a good developer.

"You can't get the same code quality as a framework" Of course, frameworks or libraries are often written by experienced developers in the industry. However, the framework's code is primarily related to framework-specific activities (component lifecycles, common refresh mechanisms and optimizations, tools, and so on) and is not related to your application. Also, even with frameworks, you can still make bad designs and write bad code. The quality of an application always depends more on the quality of the team than on the lack of a framework.

"You can't get the same performance as a framework": No, we can get better performance. Industry claims that frameworks employ complex techniques that can "improve performance" are not discussed here, as they may be primarily used to address performance flaws in framework-common solutions (such as virtual DOM).

Is it possible to develop web applications without any framework?

Without a doubt, the best performing frameworks are those that add fewer layers on top of normal code. The "optimization" of the framework is more to compensate for the overhead of the framework itself.

Conclusion

Building a web application without a framework does not mean building the framework yourself, it is about developing an application without using a common engine to:

Avoid loss of control and implicit constraints (lock-in, upgrade costs, etc.);

Optimizations can be made (performance, volume, design).

That is, to write only application-specific code (business and technical), including using the development library. The framework you really should focus on is your own framework, the one that's application-specific. This is true "business-focused" and the most effective.

It's not as hard as you might think, especially with the blessing of modern standards (mainstream browsers can support new features through polyfills when necessary).

https://javarome.medium.com/design-noframework-bbc00a02d9b3

Read on