This article will give a brief overview of our current wireless front-end architecture design and its evolution.

The main content is divided into several parts:

1) Current front-end solutions and problems to be solved

2) New challenges now faced

3) Design and selection of our front-end solutions.

I hope our experience can give you some inspiration.

1. Current front-end solutions and problems to be solved

1.1. Technical background of the current scheme

Set the clock back to 2016. We have migrated several core front-end applications from C# ASP.NET to node.js. In addition, React is added to the backboard.js front-end framework to manage the View layer, replacing the Underscore. Js template engine to achieve a complete separation of front and back ends.

Introducing React into the old framework is not as easy as described above. We need to solve two problems.

1) React is too large

React development requires ES2015 and JSX compiler support

At that time, the existing framework was already huge. Introducing React would increase the JS Size by 140+Kb, which would further slow down the first rendering time of SPA. This was unacceptable and was a major factor that prevented most companies from using React in their original front-end projects.

React is so bulky that unless it is a new project or refactoring, there is an opportunity to re-allocate THE JS Size budget. Otherwise, in order to use new technology to solve the problems of existing projects, the cost of introducing new technology must be solved first.

In order to be able to use React’s componentization technology, large chunks of render templates are difficult to maintain. We developed a lightweight version of React API compatible implementation called React Lite. Reduce the 140+Kb Size to an acceptable level of 20+Kb.

The module management tool for our project at that time was require.js. We write code for ES5 syntax, and then they run directly on the browser. There is no current compilation and packaging for Webpack/Babel.

Although react-Lite reduces the volume of react introduced, our goal is to componentalize the large render template code into smaller components for easy maintenance and increased reusability. Cannot use the JSX syntax and requires a function call to the React.createElement by hand. The React component may be harder to maintain than the template Underscore.

We tried to run the entire project with Webpack instead of require.js, because Webpack supports compiling the REQUIRE.js AMD modules. However, we soon discovered that the existing framework had a strong dependency on require.js’s dynamic and remote modules.

Dynamic module refers to that it will judge different environment and concatenate different URL address, such as:

require('/path/to/' + isInApp ? 'hybrid' : 'h5')


Copy the code

Remote modules are modules that are JS scripts delivered via HTTP requests that are not in the project’s local directory.

This makes Webpack, based on dependency analysis of native modules, difficult to use. There are a variety of other niggling issues that, while not as deadly as the two above, have prevented us from migrating our front-end infrastructure from require.js to Webpack + Babel.

Finally, we designed a downgrade scheme. The React component can be developed using the new JSX/ES2015 syntax while maintaining require.js.

We set up ES6 and ES5 directories and created a script task based on Gulp + Babel to compile ES6 modules into ES5 modules in real time based on file changes. At development time, run the gulp command.

In this way, we successfully promoted the ES6 and React development models across the team. It has established a good foundation for us to build a new front-end development mode based on React + Node.js + Webpack + Babel.

1.2 current scheme: Birth of the isomorphic framework React-IMVC

Adding node.js + React + ES2015 to existing projects has really helped our front-end development. We can write simpler and more elegant ES2015 code and no longer need to maintain.cshtml templates and configure IIS servers to run our SPA application.

There is no code or configuration in other languages in the front end project, and only JavaScript is consistent and self-sustaining.

However, we are still iterating through our front-end applications under a heavy historical technical burden. This is not a long-term solution.

We need a new front-end architecture with a 2016 perspective, not a 2012 one, that plays to the Node.js + React model more.

It needs to achieve the following goals:

1) One command starts the complete development environment

2) A command to compile and build source code

3) A piece of code that can either be rendered in Node.js server (SSR) or reused in the browser (CSR & SPA)

4) It is both a multi-page application and a single-page application, and can be configured to switch between the two modes freely, breaking the dilemma of “single-page VS multi-page” with “homogeneous application”

5) Build to generate a static file in Hash History mode as an entry file for a normal single page application (SPA)

6) During construction, code can be cut according to the route and JS files can be loaded on demand

7) Support new ES2015+ language features including async/await in IE9 and later

8) Rich life cycle, so that business code has a clearer function division

9) Internal automatic solution in browser side reuse server side rendering HTML and data, seamless transition

10) Easy to use isomorphic methods such as FETCH, Redirect and cookie to connect the front and back end request, redirect and cookie operations

Sharp-eyed students might find that just using Next. Js would do the trick.

That’s true.

But next.js won’t come out until October 2016, and it won’t become widely known until near 2018. We don’t have time to wait for the framework of the future to solve the problems of the present.

Therefore, in July 2016, I developed create-app library, which realized the minimum core functions of isomorphism, and based on create-app, Store, FETCH, cookie, Redirect, Webpack, Babel, SSR/CSR, config and other functions have been added to form our self-developed homogeneous framework, React-IMVC, which achieves the above 10 goals.

1.3 Design ideas of React-IMVC

We split each page into three parts: Model, View, and Controller. Go back to the simplest MVC mental model of GUI development. This can be seen in the framework name of React-IMVC.

IMVC’s I is short for Isomorphic, which means Isomorphic, in this case, a piece of JavaScript code that can run in both Node.js and Browser.

IMVC’s M stands for Model, which means Model, in this case, a collection of states and their state-changing functions, consisting of initialState and Actions functions.

IMVC V stands for View, which stands for View, in this case, the React component.

IMVC’s C stands for Controller, which means Controller, in this case, contains lifecycle methods, event handlers, isomorphic tool methods, and the intermediary responsible for synchronizing the View and Model.

The three parts of MVC in React-IMVC are Isomorphic, so it can do: Write only one piece of code to do server-side Rendering in Node.js and client-side-rendering in Browser.

In the React-IMVC Model, Redux mode is adopted, but some simplification is made to reduce boilerplate code writing. Where, state is immutable data, and action is pure function without side effects.

React-imvc View is React. It is recommended to use functional Component as far as possible. Side effect is not recommended.

However, side-effects are a necessary product of interaction with the outside world and can only be isolated, not eliminated. So, we need an object that does side-effects, and that’s the Controller.

Life-cycle methods are a source of side effects, Ajax/Fetch is a source of side effects, Event Handler is a source of side effects, and localStorage is a source of side effects. They should all be handled in an object-oriented way in the Controller ES2015 Classes.

A Web App consists of multiple Page pages, each of which consists of three MVC parts.

The code above implements a counter page that supports SSR/CSR. We can clearly see the design philosophy behind React-IMVC.

The Model property of the Controller class describes the initialState of the Model and defines actions for state changes.

The View property of the Controller class describes how the View is rendered through the React component, which performs data binding and event binding according to the state/ Actions provided by the Model.

When a View layer click triggers actions, it causes a state change within the Model, and a Model change notifies the Controller to trigger an update to the View layer. This makes up the classic render cycle Model of Model, View, and Controller.

So how do we support SSR?

As you can see in the figure above, it’s quite simple. The Controller contains a number of life cycles, where getInitialState is called before the Model/Store instance is created, supports asynchrony, The FETCH API provided by the Controller can be used for HTTP interface requests.

React-imvc holds the asynchronous data acquisition internally and does not proceed with the subsequent rendering process until THE SSR data is ready. These complex operations are hidden inside the framework. To page developers, they are just lifecycle, asynchronous interface calls.

In addition to getInitialState, react-IMvc provides other useful life cycles, such as:

ShouldComponentCreate: Should the page be rendered? Here you can authenticate and redirect this.

2) pageWillLeave: the page will jump to another page

3) pageDidBack: The page jumps back from another page

4) When a window is about to close

5)…

By configuring a rich lifecycle, we can slice the business code into clearer chunks.

With an index.js routing module, controller.js of multiple pages can be set according to the same path/router path configuration rules as express. js, so that different Page requests can be loaded and responded to as needed.

The React-IMvc framework takes over the Request in Node.js, matches the corresponding Controller module according to the Request path of Request. The Controller module is instantiated and SSR is performed. On the browser side, the HTML structure and initialState data are automatically reused within the framework based on the SSR content. This process is known as Hydration.

For page developers, they don’t need to worry about SSR adaptation in most scenarios. {fetch, GET, POST, cookie, Redirect} and other methods in the controller will automatically switch the corresponding code implementation according to the operating environment, which is transparent to the user.

With the isomorphic framework React-IMVC, we have carried out an innovation and standardization in the development way of front-end projects. Within a few years, a large number of old projects migrated to the new framework, and almost all new projects were developed based on the new framework, leading our team into the era of Modern Web Development, the Modern front-end Development technology stack.

2. Current new challenges and problems

When we developed the React-IMVC framework, we expected that this solution would still be applicable and not obsolete in 5 years. Now, more than three years later, there are some interesting changes in the front end. Take, for example, the emergence of React-hooks in October 2018, and the popularity of TypeScript.

These incremental enhancements do not make an SSR framework obsolete. React-imvc also duly follows with support for react-hooks and TypeScript.

What makes us pause and re-examine the design of a new front-end architecture is not that the existing solution is once again obsolete. It is that we are confronted with new problems, and existing solutions are not sufficient to address them.

At the beginning of the design of the React-IMVC framework, the main consideration was to unify the two platforms of Node.js + Browser. A piece of code that runs in Both Node.js and Browser and automatically coordinates the Server/Browser transition process. React-imvc is still a reasonable choice for Web development applications that involve the separation of the front and back ends.

When it comes to multi-terminal + internationalization scenarios, the situation is beyond the initial consideration. A product line may have multiple applications:

1) Domestic PC sites;

2) International PC sites

3) Domestic H5 sites

4) International H5 site

5) React-native apps in domestic apps

6) React-native apps in international apps

7) Domestic applet applications

8) Apps in other distribution channels, etc…

With so many application forms, each with a full-time front-end development team, the cost and efficiency are not satisfactory. React-imvc is suitable for PC/H5 homogeneous front-end applications, but lacks support for App/React-Native and applets. How to save multi – development cost has become a serious issue.

Those of you who are sensitive to emerging technologies may think that Flutter can solve the problem. Flutter is an option, but not necessarily for all scenarios and teams.

2.1 cross-end scheme investigation

To some extent, cross-end is a solved problem for front-end development. JavaScript runs on PC/Mobile, IOS/Android, and APP/Browser. Web is everywhere.

When we talk about cross-end solutions, it’s not really a matter of can, but maturity/satisfaction.

Developing the interface with HTML/CSS/JavaScript everywhere through WebView/Browser is certainly cross-terminal. However, the loading speed, fluency and other core indicators in the App cannot meet the requirements. Hence the react-Native enhancement solution: use JavaScript to write business logic, use React component to express abstract interface, but use Native UI to speed up rendering: Written in JavaScript — Rendered with Native code.

React-native offers decent IOS/Android cross-end capabilities, but it has two problems:

1) The IOS/Android cross-terminal is not even officially promised, just “Learn once, write Anywhere.” There are no officially supported cross-end compatibility issues, which need to be encapsulated and handled by the user.

2) React-Native for Web is a community solution (React-Native Web), which is not an official iterative project. The performance and experience on the Web side can not be fully guaranteed. Once problems occur, the code is difficult to debug and modify. Lack of control.

When we actually use it, React-Native is a good choice for IOS/Android apps, but there are certain risks when compiling to Web platforms.

Flutter claims to run on mobile, Web, and Desktop platforms using a set of code developed by the Google team. Very attractive indeed. It may not be suitable for our scenario at this time for the following reasons:

1) Flutter uses Google’s own Dart language, not JavaScript. All business code has to be rewritten, which is costly to learn and refactor.

2) Flutter support for the Web is currently in beta channel and is in the Phase of Preview releases, which still has some production risks.

3) The functions of Flutter mainly cover the rendering engine. In actual business development, the specific apis of IOS/Android/Web platforms need to be adapted. Not 100% of the problems can be solved by using Flutter itself. There is a lot of time and cost involved in building the infrastructure around Flutter.

Therefore, at this stage, Flutter may be suitable for startups, small and medium sized companies, or non-core projects that start from scratch in large companies.

The main cross-end solutions are summarized as follows:

1) Web/Page: The Browser experience is ok, but the App experience is not good;

2) React-Native: The experience is good in App, but not guaranteed in Broser;

3) Flutter: the experience of App/Browser is guaranteed, but the cost of learning, refactoring and infrastructure is high;

Flutter is a radical solution that uses a language and infrastructure that is new to developers in the company. What we really want is not to overturn the existing accumulation, but to make an incremental improvement on the current solution.

It is not ruled out that Flutter may become the best solution to unify the big front end in the future, but before it becomes a reality, we have to face and solve the problems of the present, not just wait for the perfect solution of the future. And while versatility is one of the problems we face, internationalization is another.

Due to the huge cultural differences between domestic and international users, we need to prepare at least two sets of products with significantly different interface styles and interaction patterns. One is for domestic users and the other is for foreign users (multilingual support via I18N).

Even if multiple applications such as Flutter are solved, we still need to think about whether there is room for unification/merging of two sets of domestic/international applications.

3. Transition from VOP to MOP

We are looking at the Model layer, which takes on the state management and business logic of the application and is the more pervasive and pure part.

We can unify the Model layer of multiple projects, but keep the View layer separate, and connect the different View layers to their corresponding Platform/Renderer.

The question becomes, how do you maximize the Model layer, get the Model layer to do as much work as possible, and write as much code as possible in the Model layer?

With this new perspective, we look at the boom in front-end development over the past five years and find an interesting observation.

We can categorize the development of the last 5 years as the orientation of Oriented Programming, or VOP.

React/React-Native, Vue/Weex, Angular, Flutter, and SwiftUI are all component-based view enhancement modes. They focus on view components and enhance the presentation capabilities of view components, from basic parent-child nested composition capabilities to state management capabilities to side effects and interaction management capabilities.

Let’s take a look at their component notation.

Function Component contains both State and View. They are inseparable. State is a local variable and is bound to View. While we can extract custom hooks that can be reused in react-native, when we use the DOM/BOM or rn-specific API to trigger setState in useEffect, they are coupled to the specific platform.

Above is the Vue SFC code, template is the View part, data/compted is the State part, they are one-to-one correspondence.

The Angular component code above, View and State management, also correspond one to one.

The State management of Flutter was implemented by the members and methods classes, which were Stateful widgets. Members and methods are inseparable in a class.

This is the code for SwfitUI. Components are also expressed by class, as opposed to the View of the Flutter and SwiftUI components in the body method.

Whether they put State/View in a function or in a class, there is a one-to-one binding between State/View. State is built around the consumption and interaction requirements of the View, which is the real core of the component.

This is not to say React, Vue, and Flutter/SwiftUI are all wrong. Enhancing component expression is the right thing to do. Suffice it to say that when State and View are tied together, the goal of maximizing code reuse in the Model layer is difficult to achieve.

We need to make state management view Agnostic, managing states and their changes in a separate Model layer, without assuming what view Framework is downstream.

In other words, we will move from Oriented Programming to Model-oriented Programming, or MOP for short.

I went from programming to View, to programming to Model.

4. MOP selection

In the current JavaScript ecosystem, popular solutions that can be used independently of a specific View framework are:

1) story

2) Mobx

3) Vue 3.0 Reactivity API

4) Rxjs

5)…

Redux, once the preferred solution for React state management, has its own DevTools support to easily trace state change history through actions. But given the amount of template code it uses, implementing a feature across multiple folders is not very convenient. There were complaints about Redux in the community. Every time React added a new feature, the community wanted to replace Redux with that new feature. There are numerous attempts to package Redux into a more user-friendly form, and even Redux officially offers a package called Redux/Toolkit.

Mobx is the next most popular solution in the React community after Redux, referencing Vue’s Reactive state management style. It can also be used independently of React or with other view frameworks.

Vue 3.0 extracts the internal ReActivity API as a standalone library, which can also be used independently or with other view frameworks.

Rxjs is a responsive data flow pattern, based on which you can implement a set of state-management solutions, used anywhere.

In general, you can choose any of the four libraries, depending on your team’s style and preferences. At the same time, it is difficult to maximize the Model layer by using only their existing capabilities without any enhancements.

Our choice is Redux.

The reason is simple. The Model layer of the React-IMVC framework used by our team is based on our own implementation of the Relite library, which itself is a simplified version of Redux mode, similar to the official Redux/Toolkit writing style. Choosing Redux is a continuation of our existing experience and some of our code.

Furthermore, we believe that Redux’s actions/Reducer contains the essential core of predictable state management, which ultimately exposes a set of update functions, Actions, with or without Redux.

For example, no matter use Mobx, Vue – Reactivity – API or Rxjs, to write Todo APP state management code, will still get addTodo/removeTodo/updateTodo update function. While Redux Devtools is a proven tool for tracking these actions, there are additional adaptation costs associated with choosing other options.

5. Our MOP framework: Pure-Model

We implemented a MOP framework based on Redux that supports a maximized Model layer called pure-Model.

Redux is simplified compared to the VOP phase, with fewer functions for the Model layer and more functions for the View. The pure-Model of the MOP phase is a reinforcement of Redux, with more responsibility for the Model layer and less responsibility for the View.

Redux itself requires state to be immutable, reducer is a pure function, and IO/ side-effects are realized by redux-middlewares. Redux-middleware, however, is extremely difficult to use and understand, splitting the code distribution of a function and forcing it into two places, making it difficult to read and maintain.

That’s a design limitation in 2015. The whole front-end community didn’t know how to manage side effects in pure Function. By the time react-hooks were released in October 2018, we had seen an effective way to add state and effect interactions to function-Component.

React-hooks are enhancements to the View layer that enable View components to express state and effect. Custom Hooks can be used for logic reuse. But the idea behind it is universal, not limited to the View layer, we can re-implement Hooks in the Model layer to get the same enhancements.

Above is the pure-Model equivalent of the react-imvc Counter function shown above. Model is no longer bound to the Controller properties along with View. The Model is defined separately and is used in the React-DOM component via the exposed React-hooks API, as well as in the React-native component.

Our demo code writes Model and View in the same JS module in order to render the code in a single diagram. In practice, the Model layer is a standalone module that is then used in component modules such as View.h5.tsx and View.Rn.tsx.

Note that there are two Hooks, one View Hooks and one Model Hooks.

SetupStore for Pure Model is a Model Hooks that define the store. CreateReactModel converts it to model.usestate for react-hooks.

So how does pure-Model support SSR? Without the Controller’s getInitialState method and fetch/ POST interface, how do you request data and updates to the store?

As shown above, we provide a built-in Model-hooks API and lifecycle functions such as setupPreloadCallback that override Http requests and events such as preload, Start, finish, etc.

Register a preload function in the setupPreloadCallback, which supports asynchro, fetching data through the Http interface and calling action to update the status. This life cycle provides the ability to preload and update data before state is consumed by external subscribers. Thus, the first time an external consumes the data, it gets a full structure.

And setupStartCallback/setupFinishCallback is by subscription and cancel the subscription in Model two callback. When pure-Models are used in React components, they correspond to the life cycle for componentDidMount and componentWillUnmount.

Model-hooks, like react-hooks or Vue- composition-API, support writing Custom Hooks to implement reusable logic, such as setupInitialCount, Can be called/reused anywhere model-hooks are supported.

We also have model-hooks like setupCancel built in, which make it easy to construct cancelable asynchronous tasks and not just Http requests. With these Hooks APIS, the Model layer code becomes clear and elegant, allowing developers to register different onXXX lifecycles and trigger different actions using different Model-hooks for different scenarios.

In addition, these life cycles are not flat methods in class, but can be grouped, sliced, encapsulated and nested in tree form, which is a more flexible and free mode.

In pure-model, reducer is Pure function, but other additional parts such as setupXXX support IO/ side-effects. SetupStore immutable/ Pure store/ Actions. Next, we build actions that support asynchrony based on store/ Actions.

All function implementations are wrapped in functions like setupStore/setupXXX, which are defined and not executed, so the createReactModel is pure and it just returns a set of functions.

On different platforms, we can inject different implementations of setupFetch, such as window.fetch in the browser, node-fetch in Node.js, In React-native we inject the global.fetch wrapper.

Pure-model takes the route of building the upper level abstraction. All Hooks describe what to do, but do not define how the lower level implementation does it. When pure-Model runs on a specific platform, this part of the code implementation is given by an adaptation and cohesion layer.

With the pure-Model layer abstraction of Redux + Model-hooks, we can put not only state-management code in the Model layer, but also effect-management side-effect Management code in the Model layer. In the View layer, you just need model.usestate to get the current state and model.useActions to get the status update functions and bind them to the View and event subscription.

In other words, the Model layer contains the function implementation, while the View layer is left with only the necessary function calls. The code for function implementation is longer, and the code for function invocation is shorter. As we continue to extract function implementations into the Model layer, the View layer and Controller layer code get thinner and thinner.

In practice, we found that the resulting Model layer contains the core business logic code of the application, which can be run and tested independently and can be used in any view framework. It is not only cross-platform, but also has cross-era vitality. When React is made obsolete by the next generation view framework, we don’t have to throw away all code; Implement an adaptation of the Model layer to the new view framework.

Code based on the MOP framework pure-Model, thus becoming the core asset of the application.

In retrospect, before View frameworks such as React/Vue became popular, the coupling between Model and View layer was always negative. A View is a thin layer, or even just a line of render(template, data). The core code manages data and events in both the Model and Controller layers.

When React/Vue became the dominant theme of front-end development, it became a popular trend to write all your code in view components, because view components were more expressive.

However, the functions of the Model layer and View layer are to some extent mutually exclusive. We need models to be independent, stable, and resilient for long iterations, whereas the View layer is fluid, data-dependent, and has an existential life cycle that changes with UI style trends.

When we implement Model layer code in the View layer, we are in a sense abandoning the core value of the Model layer.

So why is it that people have been using VOP for 5 years with no real problems?

This is because the Model layer itself is divided into several layers, front-end Model layer and back-end Model layer. The front-end Model layer connects the back-end Model layer and binds the front-end Model layer to the View layer, which only affects the stability of the front-end Model layer. However, the back-end Model layer on which the application depends remains independent, stable, and the vitality of long-term iteration.

In the stage of rapid development of front-end framework, the whole front-end project reconstruction and framework upgrade, also regarded as normal. So the coupling of the Model layer and the View layer has little real impact. This is similar to a web page memory leak that is not a fatal problem, just refresh it.

The front-end Model layer, coupled with the View layer, is becoming awkward as the front-end framework competition stabilizes and front-end project reconstruction becomes less frequent, coupled with multi-terminal and international requirements.

The same back-end Model layer can be connected with multiple applications of different UI interface styles. It is a convergent Model. The front-end Model layer actually increases with the increase of THE UI interface, which is a non-convergent Model.

The MOP framework pure-Model is an attempt to converge the front-end Model layer. In fact, it does not completely overturn the REact-IMvc and other SSR frameworks. It is still driven by React-IMvc in Browser/Node.js and react-native in App. In essence, it just changes the way the code is modularized, moving some of the code that was piled up in the View and Controller layers into the Model layer for maintenance, leaving only a small amount of code for function calls in the View and Controller layers.

Combined with the back-end Model integration capability constructed by using the GraphQL-BFF pattern, the pure-Model serving multiple ends can query graphQL-BFF on demand to accommodate the front-end and back-end data interaction at different ends. See GraphQL-BFF: Front and Back End Data Interaction In the Context of Microservices

6, Monorepo

Pure-mode is not enough either, it’s just an abstraction layer, and it’s the react-native/React-dom view frameworks that really drive the code.

That is, we will have multiple projects, each built on different scaffolding, sharing code that passes through a Model layer. Then, how to share code across multiple projects becomes an engineering problem that needs to be solved.

Distributing Model layer code through NPM and other package management services is an inefficient scheme. Any changes need to be released and NPM install or NPM upgrade will be re-installed in each project, making it difficult to use the efficiency requirements of rapid development.

A similar problem arises when placing multiple projects in multiple Git repositories: Which project’s Git repository does the Model layer code reside in? Add a separate Git repository for the Model layer. Code synchronization and versioning for N + 1 repositories would be chaotic.

More efficient and consistent code sharing can be achieved through Monorepo single repository multi-item model.

For example, we place our projects in the following directory structure:

projects/isomorphic

projects/graphql-bff

projects/react-native-01

projects/react-native-02

projects/react-dom-01

project/react-dom-02

Isomorphics project is the project where the Model layer resides, and it has its own independent package.json to manage tasks such as development and testing. Other projects in the projects directory can be built using any scaffold, supporting multiple projects built by the same scaffold. They also have their own separate development, build, and test suites.

Through soft linking, the SRC directory of Isomorphic is mapped to the SRC/Isomorphic directory of other projects. Thus, the code source is unique, but occurs in multiple projects, each of which can import the shared code. When a project no longer needs to share code with other projects, it can migrate the entire folder to a separate Git repository to do its own independent iteration.

In addition, graphQL-BFF back-end Model projects such as Projects/GraphQL-bFF are also introduced. TypeScript files of interface data types are generated through GraphQL Schema and shared among all front-end projects. We can get a more authoritative interface data type prompt, reduce the majority of the front and back end data structure and type mismatch, resulting in empty/non-empty, inconsistent type, field name misspelled case and other problems.

With Monorepo we have a convenient way to share code across multiple projects; Pure-model maximizes the reuse of front-end Model layer code; With GraphQL-bFF, we integrate the back-end Model and provide authoritative source of interface data type. With react-imvc we get the ability to render SSR and CSR in Node.js and Browser. Through React-Native, we can build a near-native APP experience on IOS and Android platforms. Together, they make up our cross-end code reuse scheme,

We thought that to solve the multi-application redundant development problems caused by multi-terminal and internationalization, we needed to use technologies such as Flutter to make a radical change. But exploring and thinking back and finding ways to tweak things can also be lucrative, cheaper and safer.

The amount of code that needs to be implemented in the new design is not that great; it is itself a new abstraction based on the existing framework. The existing frameworks React-IMVC and React-Native continue to work, but improve the Model layer and move git repository management to Monorepo mode.

There are a lot of details to overcome in actually using this pattern,

Such as Webpack/Babel/TypeScript/Node. Js/NPM tools such as support for soft links and different way, coordinate the soft links to in each frame to show its normal need to deal with a lot of compatibility.

Building, publishing, and branch management issues for multiple projects in a Git repository are new challenges to be faced.

7, outlook

We are currently in phase one, isolating the Model layer and maximizing its functionality.

In the second stage, we will layer the View layer:

1) the Container – Component;

2) Atom – Component/Atom – Element;

React-native, react-dom and even react-? And other render targets that provide some atom-Component or atom-Element. For example, div/ SPAN /h1 in react-DOM, View/Text/Image in React-native, etc. The problem of unifying them at the Atom level has already been discussed and will not be covered here.

We can keep the differences at the Atom level to maximize the capabilities of each render target, but do some unification at the abstraction level of Container.

As shown above, we use React useContext to encapsulate useComponents, inject different Banner/Calendar component implementations on different platforms, and associate them with state/ Actions in the Model.

Then, a significant portion of the code that exists in the View layer, such as component structure stacking, state binding, event binding, etc., can be extracted and reused across multiple ends. Just inject a different component implementation when each side starts. In this way, the flexibility and freedom of the lower level implementation are retained, and the stability and consistency of the upper level abstraction are obtained.

When we push this process from top to bottom, extracting all the reusable abstractions until all the underlying differences are erased, it is equivalent to implementing a cross-platform framework like Flutter. But we don’t have to build the Flutter from the ground up to a certain degree of completion before it can be used for practical purposes, like Flutter. We’re building on what we have, and we’re making money every step of the way. Also, as Flutter becomes more mature, we can replace the underlying layer with Flutter rendering while retaining the upper layer of abstraction.

Therefore, it is an approach that deals with both the present dilemma and the future development.

conclusion

After this cross-end solution, we have a clearer understanding of how the code is organized.

More than ever before, you know which code should be in the Model layer, which code should be in the View layer, which code should be reusable, which should be kept different, which problems should be solved by the runtime framework, which problems are actually engineering problems, through directory and Git repository adjustments and team work, etc.

As we force the bottom level of differences, we find less and less capacity to use.

We lose the long-term value of the Model layer when we put in the View layer what should be in the Model layer.

As we put engineering problems into run-time frameworks, our frameworks get fatter and slower and slower.

We chose to preserve the underlying differences, replacing one large, all-inclusive runtime framework with multiple, lighter run-time frameworks.

We unify the more stable parts of the Model and View layers that have long-term value and can be shared across multiple projects by constructing upper level abstractions.

This way, at every level, we have the opportunity to extract maximum value without having to accommodate compatibility.

Above, we have roughly described how our front-end architecture design went from backbone. js to pure-Model + Monorepo + graphqL-bFF + React-Native/React-IMVC. It also presents the problems, thoughts and final choices we face at each stage.

They may not be suitable for every project or team, but hopefully they will give you some inspiration or thought.