Mobile web is developing by leaps and bounds. In the yard in 2017. Mobile traffic has exceeded the desktop - more than half of all pages are now opened via phones or tablets. In 2015, Google announced a preference for mobile-friendly sites when ranking issuances, and in 2016 Yandex did it. Users spend on the Internet 60-70 hours a month from mobile devices and are not ready to compromise and use non-adaptive sites. And 2GIS is no exception. For 2 years, the growth of 2GIS Online mobile traffic was 74%, and the monthly audience exceeded 6 million people.
On April 17, we unveiled a new mobile online (“Monline”) - a one-page application available at m.2gis.ru. The application was launched in two cities: Ufa and Novosibirsk, and in the near future release is planned for the whole of Russia.
We knew that we would face three problems with mobile development:
Mobile Internet
Mobile Internet (2G, 3G, 4G) or Wi-Fi is slower and not as stable as cable.
Mobile devices
The problem of mobile phones is a weak processor. This affects JS parsing, rendering, and animation.
Mobile browsers
Some popular mobile browsers do not support some CSS properties or JS API methods. Sometimes we thought we were back in the dark times of development for IE8. The mobile Internet and the processor did not allow the use of polyphiles, which means that they had to get out on their own.
We retained the functionality of the desktop online version and made Monlyn even work on your grandfather’s dead phone near Penza. How we did it, we will tell below.
In the world of mobile web, JS-developer follows one credo: divide - cut. The final bundle of a mobile application should weigh little and be broken into pieces.
The main and only frontend framework is Preact. This is not a typo. Preact is a lightweight alternative to React that uses the same API and works with Virtual DOM. The advantages of the framework are low weight (3 kB gzip vs. 45 kB for React) and higher rendering speed. Thanks to the use of Preact instead of React, the size of our vendor has been reduced by 90%.
Preact is different from React. For example, it does not have propTypes, but this problem was solved by introducing static typing, which we describe in paragraph 3. Details of the differences between the frameworks are described in the official repository on github .
In addition, we try to write independently and not to connect third-party polyfills and heavy libraries. Required polyfiles are loaded asynchronously via require.ensure and do not fall into the bundle. Each polyfil is connected only depending on the conditions. For example, in the case of the polyfil for Android Browser - we saved 5 kB code in gzip.
Js formed. Now it's time to break it. JS code is divided into 3 groups:
The vendor was minimized through the choice of Preact, and we connect the polyfiles asynchronously and on demand. Stayed last hero. Gzipnuty app-bandle weighing 143 kilobytes in gzip. This is a luxury for mobile development. So, if a user has logged on to an organization card, it makes no sense to immediately load the code responsible for rendering the metro card or sights. To reduce the size of the app bundle and deliver as little code as possible to the client, we did a lazy download.
Requirements for code number 1 in Montline says: "The code should be as simple as possible and not know or do too much." The UI structure is based on this rule. The project has 11 containers and 85 "stupid" components. "Stupid" components are not aware of the existence of each other. Containers combine components into structures and transfer data through props. Card organization or recall, the issuance of buildings - examples of containers. 6 containers out of 11 are not connected to each other, which allows you to break the app-bundle into ... intrigue ... 6 additional chunks. When restoring, the browser loads the app-bundle and the desired chunk, and then the rest .js files that are responsible for inactive containers are loaded asynchronously. This does not block the application. After the release of a lazy download, the weight of the app-bundle was reduced by 38%.
In a mobile web, it is critical to quickly display pages. In Monline, like in 99.9% of the SPA, some of the information in the containers overlaps. Take the issuance of firms and a card of a separate organization. The output displays the title, address, schedule, etc. The same information is displayed in the company card. Such information does not change while the user is using the application. It makes no sense to wait for a response from the server if the user has already viewed this information in the past, because it can be stored on the client in a single copy and show the data here and now.
Data normalization on the client is the storage of unique data in the stack reused in containers. Storing data on the client increases the speed at which content is displayed, and normalization removes duplicates and separates static and context-dependent data.
Normalization keeps the state clean. If static data is viewed, then it is stored in the stack in a separate table. For example, if a user opens one company several times, then in the state it is stored in one instance with access by id. And the information about this company is used for any needs: on the card of the organization itself, issuances, reviews, photos, etc. The container selectors are responsible for formatting the data.
Let's give one more example. You can get into the company card through a search query, click on the section or simply recover from the link with its id. The search query or id of the category is added to the URL that the router parses during recovery. If there is something in the URL besides the id of the company, the state stores both information about it and the data on the first 10 companies matching the search query or category. When you switch from company card to issue, the user instantly receives a list of organizations and just as quickly can open the page of each company.
People do not like to wait not only for downloading content, but also for a response when sending data through forms. Storing data on the client reduces waiting time when saving information. Information in the state unleashes hands when creating optimistic interfaces. The optimistic UI design shows the end state before the application actually finishes (or even starts) the operation. After the data has been submitted, the user instantly receives information about their saving due to the fact that the user's information is stored in the stete and sent to the server in the background. Only if the server received a response with an error, will the application notify the user with the appropriate message, otherwise the server’s response is not displayed on the screen.
34 developers took part in the creation of Monline. 2,000 commits were merged into the master, 77,000 lines of code were written and 1425 files were created. The project involved people from other teams, the guys came to the internship. We wanted to speed up the development process, make the code understandable and documented. Therefore, we decided to abandon the dynamic typing in JavaScript.
The client part of the application is written in TypeScript. Static typing is the main advantage of TypeScript over JS. It hits hands in case of errors during compilation, documents the code from the inside and facilitates refactoring and debugging.
To control the state of the application in the project uses Redux. Redux is combined with TypeScript. The developer knows what is transferred to payload or meta and, accordingly, what comes in the reducer. For example:
export const setScrollTop = (payload: number) => ({ type: APPCONTEXT_CHANGE_SCROLL_TOP, payload }); export const setErrorToFrame = (errorCode: ErrorCodeType) => ({ type: APPCONTEXT_SET_ERROR_TO_FRAME, payload: { errorCode } });
TypeScript simplifies working with reducers (pure functions that compute a new version of the state and return it). For example, the reducer, which is responsible for updating information about the current context of the application in the stack, processes 69 actions. At the output, we get a method with a switch of 100 lines of code that returns a new state. It is crucially important to process only the necessary actions in such a large canvas.
export default function (state: AppContext = defaultState, action: AppAction): AppContext { switch (action.type) { case APPCONTEXT_ADD_FRAME: return appAddFrame(state, action.payload); case APPCONTEXT_REMOVE_ACTIVE_FRAME: return appRemoveActiveFrame(state); .... case APPCONTEXT_HIDE_MENU: return { ...state, isSideMenuShown: false }; default: return state;
Discriminated Unions helps to avoid confusion with a set of actions in the reducer and data in payload or meta. In the code above, you can see that the action argument is described by the AppAction type, which looks like this:
export type AppAction = AppAddFrameAction | AppRemoveActiveFrame | AppChangeFramePos | AppChangeMode | AppChangeLandscape…
AppAction combines 60 interfaces (appAddFrameAction, AppRemoveActiveFrame, etc.). Each interface describes an action. The type (type) of an action - a string literal - is a discriminant. It determines the presence and content of the internals of an object, such as payload or meta.
export interface AppAddFrameAction { type: 'APPCONTEXT_ADD_FRAME'; payload: Frame; } export interface AppRemoveActiveFrame { type: 'APPCONTEXT_REMOVE_ACTIVE_FRAME'; } export interface AppChangeFramePos { type: 'APPCONTEXT_CHANGE_FRAME_POS'; payload: FramePos; }
So TypeScript understands that for an action with the discriminant 'APPCONTEXT_ADD_FRAME' you need to transfer the load with the Frame interface, and in the case of 'APPCONTEXT_REMOVE_ACTIVE_FRAME' you do not need to transfer anything.
Preact is also compatible with TypeScript. In Preact, there are no reactance propTypes, which solve the problem of type checking on a component. But TypeScript makes up for the loss. For example, the developer knows what is passed through the component component and what is stored in the stack.
export interface IconProps { icon: SVGIcon; width?: number; height?: number; color?: string; className?: string; } export class Icon extends React.PureComponent<IconProps, {}> { constructor(props: IconProps) { super(props); } public render() { const { color, icon } = this.props; const iconStyle = color ? { color: this.props.color } : undefined; return ( <svg width={this.props.width || icon.width} height={this.props.height || icon.height} style={iconStyle} className={this.props.className}> <use xlinkHref={icon.id} /> </svg> ); } }
TypeScript is documented and has a sandbox. Of course, the language is not omnipotent, as of July 2017, there are 2200 issues open. The Microsoft product does not support some of the innovations in ES6. But slowly and surely these problems are solved in each new release.
In mobile online - 85 "stupid" components. "Stupid" components - visual entities responsible for the presentation of the data. First of all, we wanted to separate the layout and integration of these components in the application. This would speed up code review and testing by the developer resource. To achieve these goals, use Makeup.
')
Makeup is a graphical interface for quick and comfortable manual regression testing of typesetting. You can read about the instrument in detail here , and touch it here .
Using Makeup, the layout of components is done on a separate host with locked data without being tied to an application. This allows you to test the component's visualization at the design level, adjust the layout for pixel perfect and only later integrate.
So in Makeup, there are differences between what the designer drew and what the developer folded.
And so we check the “eleventh-grader” and see that nothing has gone:
To summarize We have done a lot of work to reduce the bundle and increase the page loading speed. Data is stored on the client. The vendor weighs 90% less than it initially could. The bundle does not include unnecessary libraries and polyphiles, and the necessary ones are given in pieces on demand. TypeScript gives you more control over the application, and Makeup makes it easier to work with the visual component of Montline.
There are plans to break the “fat” modules depending on the context (for example, divide the card of a company or a geoobject into separate chunks) and try to break down the reducer.
The article is devoted to the JS code. But the development credo "Delhi - reduce" extends to the CSS bundle. In the future, we also plan to do split-style styles.
Source: https://habr.com/ru/post/333016/
All Articles