background

Most of the new Web projects in the team now use Vue or React, plus RN, which are heavyweight frameworks but too large for small Web pages. Some of the earlier projects used more primitive HTML page building techniques, but the business logic was almost impossible to reuse. I have done several small Web pages in the past six months. While constantly learning front-end knowledge, I am also reconstructing and exploring possible better solutions for small Web projects. This paper gives an overall description of the previous work.

Objectives and positioning

The biggest difference between a small Web page and a Vue/React project is that it does not need to support a heavy form like SPA. Based on the principle of Minimum Viable Product (MVP), a small page should be Viable as long as it meets the requirements. So when refactoring existing raw Web pages, the following two aspects take the highest priority:

  1. Continuously improve project reusability and maintainability;

  2. Continuously improve front-end performance, here mainly loading performance;

For the first point, the componentized code structure is the most feasible idea at present. For the second point, on the premise of achieving the first point, minimal third-party dependencies and good packaging methods are a must.

Evolution of project structure

The small Web page project structure and packaging method described in this article resulted from several project refactorings.

The first edition

The first version of the project was basically based on the original HTML+JS+CSS. To make the project code more maintainable, the first thing to consider are two things that need to be done:

  1. To make the page content maintainable, JS template needs to be used;

  2. Due to complex services, fine division of labor, and various interfaces, the Data Access Layer (DAL) needs to be separated.

For the first question, go with Mustache Library. The reason is that the syntax is minimalist and easy to learn, and the underscore/ EJS type of template syntax is also popular. To keep the content page illogical and simple, Mustache’s advanced version of Handlebars is not used. The second problem is the conclusion made after understanding the company’s business and project code. This is a refactoring of existing code, with the main purpose of separating responsibilities, isolating complex and volatile interfaces, and leaving the rest of the code to focus on solving business problems. When I started coding, I found another serious problem: I needed to separate the code that managed the content template from the main business logic, so I came up with the MVP(Model View Presenter) pattern. In short, this is a weakened version of MVVM mode without the view-ViewModel two-way data binding. As shown below:

In small Web pages, there is generally no Model layer. The Presenter part of the page is only responsible for controlling the rendering of the interface through parameters and exposing View layer events as components. According to this idea, the first version of the project structure is basically out, as shown below:

Introduce the Webpack 2 +

The first version of the project is structured enough to handle the needs of small Web pages without introducing much complexity. But raw Web pages are inherently bad for modular development, and there is a fundamental problem:

  1. Code decoupling will make the project file structure clear, responsibility separation, is conducive to maintenance;

  2. The packing results need to be compressed into a single file to improve the loading performance;

In Web page development, the two form a paradox. Therefore, a packaging mechanism needs to be introduced to decouple project code from the packaging files. Forget old Gulp and Grunt, FIS3 and Webpack 1.x are mostly used in existing projects. The former is domestic and very easy to use, while the latter is more difficult, but like other foreign open source projects, they can always do 50% of the documentation of a software well. After looking at the documentation for Webpack 2.x, I was almost sure to use this packaging mechanism, which has the following advantages that I can’t stop:

  1. Import syntax is supported natively. This will completely get rid of the file structure is not good management of the problem, object-oriented, modular and everything can be introduced, finally can comfortably write code;

  2. Support for Tree Shaking. This was originally a rollup packaging feature, but now Webpack has it too;

  3. The Webpack configuration file is complex, but once you understand it and combine it with the plug-in mechanism, you will find that it has great potential and is quite flexible to use.

After the introduction of Webpack 2.x, different functions were separated in a single file and the interface between modules became very clear, but it needs to be improved.

Refer to the Vue project structure

JavaScript files are well decoupled with the inclusion of packaging, but templates are all on the home page, styles are all in one file, and dependencies are messy and difficult to manage. A good way to improve it is to look at other excellent projects, such as Vue, which has a very good project organization structure. The major changes of the new project are as follows:

  1. Really separate the components out. With Mustache template for component content, Less syntax for style, and JS part that controls the rendering logic of the component without any business logic as much as possible, you get a.vue file. This approach can also support other templates and CSS syntax, such as EJS or SCSS, by modifying the Webpack configuration or using appropriate plug-ins;

  2. Choose to support multiple page entries instead of routing. This simplifies the complex URL structure in a SPA, while packaging the results without routing logic. Another advantage of this is that it will be very convenient to introduce a simple VERSION of SSR later, routing is nginx’s business;

A detailed description of this part of the project structure can be found directly below. The structure diagram is as follows:

It should be noted that the State Store in the figure is not available at present, and it is put here mainly for good looks :). If vuex/MobX/Redux and so on were added, it would be complete. At present, because the business logic is very simple, the violent solution of state and so on is ok. App.js, on the other hand, handles the common business logic of the project, freeing the portal to focus on the content.

Project Structure Description

The directory structure

The project directory structure is as follows:

----build # Webpack configuration file... - SRC -- -- -- -- -- -- -- -- assets # resource file -- -- -- -- -- -- -- -- components -- -- -- -- -- -- -- -- -- -- -- -- GoodsInfo # commodity information component GoodsInfo. MST # component templates. Use Mustache Goodsinfo.js # for component rendering and manipulation logic. General business irrelevant GoodsInfo. Less # component style -- -- -- -- -- -- -- -- -- -- -- -- RiskPromt... ------------ShareHeader ... ------------SharePanel ... Utils. Js # collection of business-independent, view-layer-dependent helper methods --------dal # data access layer index.js # entry file. Json # getInfobyid. json # getInfobyid. json # Mock methods can be generated in index.js --------Main # default page entry main.html # page template main.js # page business logic main.less # page style --------MainBanner # with bottom Banner page entry... App.js # Extract business logic common to multiple pages, such as concrete implementation of sharing functions. common.js # app-level helper methods set common.less package.json readme.mdCopy the code

Third party dependencies

With the help of Webpack 2+, the project selected the following open source third-party libraries as its base dependencies:

  1. Es6-promise: Using promise can make the code clearer and easier to maintain;

  2. Axios: Vue’s official recommended alternative to vue-Resource;

  3. Mustache: The template library used by the project

An SDK maintained by the team was also used:

  1. @zz/ zz-jssDK: Provides an interactive interface between the Web page and the App client

  2. @zz/perf: performance statistics tool

Since Axios officially insists on not integrating non-standard JSONP requests, it is necessary to introduce jSONP third-party open source libraries for existing interfaces that only support JSONP requests. These are project file dependencies. Development dependencies, the use of third-party libraries are basically Webpack-related, including Less file parsing module. Babel-polyfill was not introduced for ES6 syntax development because of the risk of unnecessary additional packaging code.

Configure file loading rules

Under the semantics of Webpack, all project files are a resource for JavaScript to use, so any resource can be handled by simply configuring the appropriate Loader. This section briefly describes the configuration of loading and parsing rules for different types of files in a project. The details of Webpack configuration are not covered here, but can be found in the official documentation.

  • Resources and pictures

To load common resource files, use file-loader. For image files, use the recommended URL-loader. The loader has an option to embed the image as a DataUri in the package file if it is smaller than the specified value to reduce additional HTTP requests, and the project sets the specified value to the usual 10K. Here are the rules:

  • The style file

The default style file in the project is Less, which uses two features of the library:

  1. CSS variables can be easily used, typically defining common pixel sizes;

  2. Hierarchical style description;

The Webpack configuration also retains the ability to load CSS files, and SCSS files can be added later. Here are the rules:

In the same project, because CSS /LESS/SCSS files have dependencies, it is highly recommended to use the same technology. For individual components, it is not possible to write a Webpack Loader that supports.vUE type component formats as Vue does. Loading style files requires explicit introduction of.less files in the corresponding.js file, such as:

  • Template file

Project templates come with Mustache, and with Webpack, the template content is placed in a separate file with a custom suffix of.mst, and the file content is still HTML with the root tag

For automatic parsing and loading of Mustache templates, there is an open source implementation of Mustache Loader on the web, but the attention is too low, and htMl-loader is sufficient:

  1. Load the.mst file and compress the contents;

  2. The files in theimg:srcEqual-relative path attributes are automatically replaced with absolute destination addresses.

You can do the same for other template languages, giving you the flexibility to use different template libraries in your project. It is important to note, however, that it is best to use only one template language in the same project for ease of management and without increasing the size of the package file. Loading.mst templates into a page is similar to the.less method. Import it explicitly in the corresponding.js file, and then extract the template content with the extractTemplate method:

One advantage of this explicit introduction is that you can manually control different templates and styles. In actual production requirements, content and style changes are frequent, while functional logic changes more slowly, making it more flexible to reference different versions of templates and styles through JS. It would also be nice to abstract this set of management mechanisms out and configure them separately.

  • Page file

Page files also exist as templates in Webpack and are parsed the same way as templates, as described above. Because it is a page entry file, the HtmlWebpackPlugin plugin is also needed to be configured in Webpack. In the following configuration, the project has two different page entries, so two HtmlWebpackPlugin instances are required:

The home page is loaded every time a user accesses the Web page. Therefore, a smaller home page saves traffic. If you look at the index.html of the Vue project, you’ll see that there is basically a skeleton in the component. The project configuration itself does not assume this, so it is possible to write everything on the front page.

packaging

The main package configuration of the project has been described in the previous section. For other specific configurations, please refer to the official documentation. Using the final package results of this project structure, all deployment files including images did not add up to more than 130K. In browsers, full page load network traffic is less than 70K due to Gzip.

Data access layer

As mentioned earlier, the main reason for having data requests as a separate layer is to separate out the complex and variable data request interfaces, but also the benefit that mock data can be handled uniformly here.

Interface encapsulation

The same interface may be requested in many places in a project. For a single interface request, there may be different approaches, such as Ajax, FETCH, JSONP, AXIos, or even the jQuery library; Some GET, some POST; Some need cookies, others don’t; The format of the returned data may not be uniform. JavaScript logic only cares about input and output, and keeping these request details in a separate place keeps the main business logic simpler. When used in a project, you simply call the method as a Promise. Example code for interface encapsulation is as follows:

Encapsulating mock data

When the front and back end are co-developed, the interface needs to be defined first and mock data examples need to be provided. So encapsulating mock data in the DAL layer saves a lot of work. The mock data is stored directly in the project as a.json file, imported in the DAL entry file, and supplied externally using a factory method. Here is the relevant code in the entry file:

Use the interface through DAL

With the DAL layer’s aggregation of the requested interfaces, it’s easy to use elsewhere.

Componentized development

The components of a small Web page have a similar structure to a.vue file, but are split into three files:

  1. Style. The content and usage are basically the same;

  2. Template. The latterVueHas its own template syntax, which is used by the formerMustacheOther templates are also supported. ifVueThe template loader is separate from the template loader, which could theoretically be taken and used.

  3. Control logic. The JS logic is a little bit different,VueThe framework has its own perfect bidirectional binding mechanism, and its interface and lifecycle are designed around it (here only).vueFile for discussion of classesReactThe usage mode is largely set for the convenience of the pull user). The simplicity of small Web pages puts a lot of emphasis on component initialization and rendering;

The positioning of components in small Web pages is clear, i.e. only for page rendering and interaction, so the design of external interfaces is not complicated. If the component follows the MVC pattern, it is difficult to discuss because the Controller itself is “boss” and can have a lot of behavior. Presenters and ViewModels are relatively simple. They differ only in their internal mechanics, but in their external behavior. There are two kinds of interfaces for components in small Web pages by default: to accept pure data parameters (props); Publicizes the event interface. Compared to the more advanced Vue, there is one less Slot Slot function.

Using the component

The way to use components is straightforward, look at the code:

One init method in a component does not cover all the requirements, because the init method in a project contains not only the component rendering logic, but also the event binding logic. When the component data content is updated, a render or update method needs to be extracted and called separately to update the interface. It’s not like Vue’s bidirectional data binding magic, so it’s a hassle. Using the events provided by the component is also simple, with the following code:

Here, the parameter of the Event handle takes the form of Object data (Event E). Where data represents the source of the event, which can be the ViewModel of the clicked object, or simply, the raw data represented by the clicked object. E is the HTML event parameter.

Component parameter processing and rendering

Binding a component internally to a specific template was illustrated in previous examples. When rendering component content, you also need to process parameter content and render it to the specified place on the page. Here’s the code:

In the constructor, we first define the props parameter format and give it a default value. In the init method, the parameters in data are assigned to props, where there is usually data conversion logic. Finally, render the components directly. As you can see, if you want to use another template engine, it is easy to replace. If an SSR server rendering component is used, the various template libraries can be put in and a factory method can be used to automate the processing. The component argument is named props, which is a complete copy of Vue /React. Because their functions and positioning are basically the same, and most of the official best practices are recommended here. Specific ideas for doing this are as follows:

  1. Small projects can’t do itVue/ReactParameter verification function, but explicitly expressedpropsParameters have the function of self-description document, which parameters and their types are clear at a glance;

  2. Also given in the constructorpropsDefault value. If there is no parameter, the component has the default display mode.

  3. There is only one parameterdataObject.VueThe recommended parameters all use basic types, but when the content is large, there are many attributes, which will not reduce the complexity of use.

  4. propsEach attribute in cannot be an object, onlyInteger,String,Boolean,ArrayAnd other basic types;

Public event

Encapsulating the triggering of events in components is also intended to reduce business complexity. Many Web projects operate directly on page content, with user interaction, content processing, and business logic all coupled together, where components encapsulate user interaction and provide an event interface. The code is as follows:

An event callback handle, clickCallback, is stored inside the component and triggered by data binding for user click events when the component is initialized.

conclusion

This paper simply describes the positioning of small Web pages, through the exploration and evolution of small Web pages to explain the current project structure design ideas, and its details are described in detail, focusing on the introduction of data access layer and componentized development. The current project is not final, it’s just a prototype of alpha, and there are a lot of things that need to be improved:

  1. Optimized for the first screen time, such as SSR support;

  2. Continue to improve the package deployment scheme, flexible support multi-page deployment, to achieve or close to the effect of offline applications;

  3. Some good ES6 grammars are worth supporting, and you need to find a way to gradually introduce specific grammars at the packaging level;

  4. Promising based syntax is worth a lot of adoption at the code level;

  5. Webpack is good, but not good enough. Hopefully the plugin will be more mature and rich.

There may be many points left unconsidered, but the actual needs are always the highest priority. As long as you keep refactoring and improving, software will stay alive