📜 ⬆️ ⬇️

The Legend of the Absolute Framework

Recently, the trend of “disappearing frameworks” is gaining popularity, the driving force of which, without a doubt, can be considered SvelteJS - buildtime-framework and compiler in vanilla javascript.

Despite the fact that conceptually Svelte is very simple, and even simpler to use, many developers wonder what the killer feature of this framework is, and the whole approach? Why isn't this “yet another javascript framework”?

In this article I will talk about one of the many Svelte superpowers that can seriously make your life easier.
')
Let's see, but first I will tell you one legend ...



The Legend of the Absolute Framework


Some frameworks were created by guys from Google and Facebook, others by cool dudes, but all under the keen "attention" of Rich Harris .

Nine frameworks were created for humans, seven, apparently for gnomes. Three more frameworks (react, vue, angular) were for elves.

After creating frameworks and implementing them in thousands of projects, Rich Harris himself and secretly created one framework ...

One framework to rule them all,
One framework to find them,
One framework to bring them all
And together bind them.

- The Lord of the Frameworks

Problem


I am sure many of you who have been seriously and for a long time engaged in front-end development have repeatedly encountered the problem of choosing tools for your current and / or next project.
The variety of all sorts of packages, utilities, whales, frameworks, libraries and other solutions, rolls over like never before. And most importantly, this whole movement continues to accelerate.

All this, one way or another, concerns the choice of the framework. Probably not mistaken if I assume that only a few modern teams and companies are starting new projects without using any js framework. Of course, if we are talking about modern web applications, and not just about web sites. And all would be nothing if so much did not depend on this in your project.


Judge for yourself, the composition and characteristics of the team largely depend on the chosen framework, and the whole hunting process strongly depends. Sometimes even the budget and terms depend on it. In short brrr.

But the real problems begin, if somewhere in the middle of a project, you understand that you have made an incorrect choice. Something has not grown together and not twisted. The framework required a little more time to master, a slightly larger team, turned out to be a little less fast, didn’t fit your goals or style of development, etc. And the most important thing is that your project is now 100% tied to this framework and you can’t just take it and rewrite it on something else.

It is even more offensive when, nevertheless, after completing the project with success, you understand that you are not very satisfied in general. And probably, you would not want to write the next project on the same framework. So, all those “reusable” solutions, to which we are striving, can be thrown into the pipe.

Actually, the hell with it with a business code that implements a specific business problem, and it works fine. But after all, you wrote such a cool "% insert your% with blackjack and girls with reduced social responsibility", and so you wanted to use it in your next project, but this infection is closely tied to the current framework from the one type that you already have.

Another variation of the same problem - imagine that you are a large company, like Yandex. You have a houilion of projects, even those of which only a few employees know about, and each project has already experienced all that I described above. The problem is that all these projects sit and hate the different frameworks that they chose initially.

And then there is your wonderful guide, decided to compete with Google Material Design and send you on a crusade to the motley interfaces of your projects in order to bring them to a common denominator. Smart designers already draw new buttons and selectors and write thousands of pages of guidelines for the new single UI-kit of your components. Hurray comrades!

Not life, but a fairy tale, right? It remains only to come up with how to pull all these new components on all those projects that you have already written in all possible frameworks. If there is really a lot of time and money and there is an aesthetic desire, and most importantly, faith, that “everything must be unified”, then you can put a couple of dozen teams to rewrite it all again, for example, with React. This is right, because the sad shit on which you wrote the last 2-3 years is already morally obsolete, but React will be eternal. Oh well)

There is another way. You can write a wonderful new UI-kit on one framework, create a library of reusable components, and then just use this UI-kit in all your projects. Cool sounds? Of course, but one problem remains - runtime.

If you have a project written in Angular (~ 500Kb), and you decided to write a UI-kit in React (~ 98Kb), then drag and drop the UI-kit itself into each project on one framework, even with a bunch of dependencies let's say it doesn't look optimal.

Decision


On the help we come the very "disappearing" frameworks, without runtime. The main condition here is that they are as isolated as possible in terms of their ecosystem and have external integration mechanisms and corresponding APIs.

A great example of such a framework is SvelteJS, about which many articles have already been written on Habré .

So, imagine the situation that we have an application on React. Perhaps he is tired of us, and we want to get rid of him, but to rewrite everything at once is unallowable luxury. Or maybe some parts of the application need to be improved or refactored. Well, or we decided to make a single component library, and therefore now we will write all the components on Svelte and use them in all projects. Submitted? Yes, of course not, no one has such a fantasy. Let's take a look at a real example.


Disclaimer
Just want to draw your attention to the fact that I am not a React-developer and last time I “felt” React in the distant 2015. Therefore, I assume that the way I wrote part of the example on React can hurt the feelings of believers in reactors. I will forgive you not to judge strictly, especially since the meaning of the article does not change.


So, the task is to introduce into the React application a ready-made Svelte component, without changing the component itself and without spinning additional runtime into the application. For example, I’ll take a component that searches GitHub users, which I wrote for the previous article "How to search GitHub users without React + RxJS 6 + Recompose" .

The code of this component can be viewed in the REPL , and the code of the example from this article in the repository .

Create React App


To begin with, we will create a new React project using the de facto standard tool - create-react-app :

npx create-react-app my-app cd my-app npm start 

Ok, if you go to port 3000, it seems to be working.

Customize Svelte


If you don’t know anything about Svelte, then I’ll say this, in the context of the task, Svelte is just one more step of your collector (webpack / rollup / gupl / grunt / etc), which allows you to write components in SFC format and compile them in vanilla javascript.

In the Svelte community, Rollup is more preferred, which is not surprising, since they have one author - Rich Harris. However, since the CRA uses the webpack, we will configure Svelte through it. To do this, you first need to remove the webpack configs from react-scripts to the project so that we can change them. This is done using the built-in command:

 npm run eject 


As far as I know, this is not a kosher approach, but for example this is the most convenient option.

Now that webpack configs are at the root of the project, Svelte can be installed:

 npm i --save-dev svelte svelte-loader 


Pay attention to the flag --save-dev , remember yes, that there is no runtime.))))

The final touch, you need to connect the corresponding loader to the configs:

  { test: /\.svelte$/, use: { loader: 'svelte-loader', } }, 


In general, it is customary in the Svelte community to write component files with the .html extension, because the Svelte component is a valid HTML file. However, to avoid possible collisions, in some cases, it is better to use the custom format .svelte file.

So we did, now all .svelte files connected to the project will be intercepted by this loader and compiled by Svelte.

Writing the Svelte component


First, it is better to set up a code editor, for example, so that it applies the html syntax highlighting to files with the appropriate extension. Something like this is done in VS Code:

  "files.associations": { "*.svelte": "html" } 

Now we create a daddy ./src/svelte_components/ and there is a folder of the component itself. After that, we simply transfer all the files from the REPL example to this folder, simultaneously giving them the new .svelte extension, and the file App.html is called Widget.svelte.

It should end up with something like this:


In the same place, we create the index.js file, in which we will have the Svelte and React integration code.

We integrate


Probably now you want to know what is magic? The magic is that we have already done all the magic. Magically, isn't it?

Seriously, now we can use Svelte components in our React application as completely ordinary JS constructors, which means the integration code with Svelte will be no different from integration with any other standalone liboy. The React documentation even contains a section dedicated to this: Integrating with Other Libraries .

The integration code might look like this:

 import React, { PureComponent } from 'react'; import Widget from './Widget.svelte'; export default class extends PureComponent { componentDidMount() { const { username } = this.props; this.widget = new Widget({ target: this.el, data: { username } }); } componentWillUnmount() { this.widget.destroy(); } render() { return ( <div ref={el => this.el = el}></div> ); } } 

So we just wrapped the code of our complex Svelte component into a very simple React component, which simply creates a new Svelte instance of the component, passing the element for mounting and the data from the props when creating it. In addition, we do not forget to dismantle Svelte component in the componentWillUnmount hook.

The only thing that we haven’t done yet is not synchronized the values ​​of the state of the components. That is, if the superior component has thrown the other props into the component wrapper, then they should apply to the Svelte component. Conversely, if the data was modified inside the Svelte component, it should be rolled back.

To do this, we will assume that the upstream React component will transmit the onChange callback, which we have to pull when there are changes inside, and we will expect changes in the props of the wrapper component in the componentWillReceiveProps hook. Let's do that:

  componentDidMount() { ... this.widget.on('state', ({ current: { username }, changed }) => { if (changed.username) { this.props.onChange({ username }); } }); } componentWillReceiveProps({ username }) { this.widget.set({ username }); } 

Here we used the built-in event state , which fires every time the state of the Svelte component changes. The object containing the current state of the component ( current ), the previous state ( previous ) and the list of changed properties are transferred to the callback. Accordingly, we simply check if the username has been changed and call the onChange callback if it is.

In componentWillReceiveProps, we set the new username value using the built-in set () method.

In addition to the built-in, Svelte components can implement custom events and methods. It is these pleasant features that make it possible to describe the interface of a component and quite conveniently organize communication with the “outside world”.

We use


Now we will try to use our widget directly in the React application. To do this, edit the App.js file generated by the starter:

 import React, { Component } from 'react'; import './App.css'; import GithubWidget from './svelte_components/GithubWidget'; class App extends Component { constructor() { super(); this.state = { username: '' }; } handleChange = (state) => { this.setState({ ...state }); } render() { return ( <div className="App"> <header className="App-header"> <h1>Github Widget for: {this.state.username}</h1> <GithubWidget onChange={this.handleChange} username={this.state.username} /> </header> </div> ); } } export default App; 

More shortly we use as normal React a component. And the result is:


Already not bad, right?) Please note that the username value that we enter in the widget text field is immediately forwarded to the React application.

We will finish


Let's now teach our widget to search and display not only the GitHub user card, but also the repository card.

First of all, you need to create a new component Repo.svelte, which will draw the repository card. For the sake of simplicity, I just copied the template and styles from User.svelte and adapted it to the repository data. However, theoretically it is a separate component.

Next, you need to teach the control component Widget.svelte to switch these two types of cards on the fly. In addition, you need to teach him to pull different requests for the user and the repository.

We will use one field to enter and define the type of data by the presence of "/" in the value. That is, if you need to search for a user, enter its username, and if the repository, then enter the username of the user, then "/" and the name of the repository.

At first glance, it looks rather confused, but on Svelte the solution will take literally 5-6 lines of code. First, let's connect a new component and an API method that we wrap in debounce:

 ... import Repo from './Repo.svelte'; ... import { getUserCard, getRepoCard } from './api.js'; ... const getRepo = debounce(getRepoCard, 1000); 

Next, create a computed property that will determine what type of card we should show:

 computed: { ... repo: ({ username }) => username.includes('/'), ... } 

Now add an API request toggle:

 computed: { ... card: ({ username, repo }) => username && (repo ? getRepo : getUser)(username), ... } 

And finally, the switch components of the card, depending on the type:

 computed: { ... Card: ({ repo }) => repo ? Repo : User, ... } 

In addition, to dynamically replace components, we need to use a special tag Svelte, which draws the component, the value of which is transferred to the this attribute:

 <svelte:component this={Card} {...card} /> 


Works. Notice? We are already writing to Svelte inside the React app! )))

Now let's teach our widget to hide the input field and try to enter username not inside the widget, but inside the React application. You never know where our business logic will get this value.

We introduce a new search property, the default value of which will be false. Depending on this property, the input box will be displayed or not. By default, accordingly, the field will not be.

 {#if search} <input bind:value=username placeholder="username or username/repo"> {/if} ... <script> export default { ... data() { return { username: '', search: false }; }, ... }; </script> 

Now in App.js, we will create an input field on the React side of the application and write the appropriate input event handling:

  ... handleUsername = (e) => { this.setState({ username: e.target.value }); } ... <h1>Github Widget for: {this.state.username}</h1> <input value={this.state.username} onChange={this.handleUsername} className="Username" placeholder="username or username/repo" /> 

And copy-paste into the folder with the widget here is such a svg spinner on Svelte:

 <svg height={size} width={size} style="animation-duration:{speed}ms;" class="svelte-spinner" viewbox="0 0 32 32" > <circle role="presentation" cx="16" cy="16" r={radius} stroke={color} fill="none" stroke-width={thickness} stroke-dasharray="{dash},100" stroke-linecap="round" /> </svg> <script> export default { data() { return { size: 25, speed: 750, color: 'rgba(0,0,0,0.4)', thickness: 2, gap: 40, radius: 10 }; }, computed: { dash: ({radius, gap}) => 2 * Math.PI * radius * (100 - gap) / 100 } }; </script> <style> .svelte-spinner { transition-property: transform; animation-name: svelte-spinner_infinite-spin; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes svelte-spinner_infinite-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } </style> 

And apply it in the widget to make it quite beautiful:

 ... {#await card} <Spinner size="50" speed="750" color="#38b0ee" thickness="2" gap="40" /> {:then card} ... 

In my opinion, it turned out not even very bad:


The top cap with a black background and a field for input is the React application, the white block below is the Svelte widget. These are the pies. )))

Repository

findings


Svelte is a great tool for developing modern web applications based on the component approach. In addition, it can quickly and conveniently write reusable standalone UI components and widgets that can be used in any web application, even in conjunction with other frameworks. It is also perfect for microntends .

Svelte is perfect for you if:


  1. You want to start a new project and do not know which framework to choose for this.
  2. You have a project, it works and it is better not to touch it. New components and modules, you can write on Svelte and seamlessly integrate into existing code.
  3. You already have a project, but it is outdated partially or completely and / or requires serious refactoring, up to a complete rewriting. You can start rewriting it in parts. In this case, you do not need to come up with complex configurations. You simply take some component, rewrite it on Svelte and wrap the new component with the old one. However, the remaining parts of the application do not even know about the changes.
  4. You have several projects on different code bases and at the same time, you would like to have a single UI-kit and use it in any of these projects. Write a UI-kit on Svelte and use it anywhere. It's nice.

Want to learn more interesting cases? Join our Telegram channel !

UPDATE: thanks justboris for the right question . Continuing the example:

 import React, { PureComponent } from 'react'; import Widget from './Widget.svelte'; export default class extends PureComponent { componentDidMount() { ... this.widget = new Widget({ target: this.el, data: { username }, slots: { default: this.slot } }); ... } ... render() { return ( <div ref={el => this.el = el}> <div ref={el => this.slot = el}> {this.props.children} </div> </div> ); } } 


 <GithubWidget onChange={this.handleChange} username={this.state.username}> <p>Hello world</p> </GithubWidget> 

Source: https://habr.com/ru/post/420113/


All Articles