As mentioned in the previous article “SSR Transformation Practice of Tmall Auto Dealer Details Page”, in order to avoid affecting online applications, our integrated application (hereinafter referred to as SSR application) is a new one based on the original CSR project. application repository.
background
When there is a new demand iteration for the business details, and the CSR warehouse changes, the SSR application will also change accordingly. The change process is as follows:
That is, with the continuous iteration of requirements, the technical side needs to maintain two code warehouses and two R&D applications at the same time, and the cost of testing, CR, and release will double.
This has a heavy maintenance cost both for the front end and for testing. In order to solve this problem, initially we wanted to keep the consistency of SSR and CSR applications on the code side, and only configure the switch ssr in build.json:
{ "targets": [ "web" ], "web": { "ssr": true, // Let the warehouse application publish mpa or ssr resources by adjusting the ssr configuration "mpa": true }, }
After a local test run, there is no problem with the development and construction after switching the configuration. However, when accessing the R&D platform of Ali Group, we encountered obstacles: The platform does not support accessing two applications in the same code warehouse. It was later discovered that the SSR application will publish the CSR link of the relevant page at the same time when it is released, so that only one application needs to be maintained on the R&D platform and released once. This gave me the hope of merging warehouses and applications. The expected merged process is as follows:
The following is our transformation process.
routing configuration
From the user’s point of view, routing is an important part of web links. For example, there are webpages a, b, and c, respectively through www.taobao.com/a.html
, www.taobao.com/page/b.html
, www.taobao.com/page/blog/index/c.html
to visit, the part marked in red after the domain name is the route of the page.
From the perspective of development, routing is not only related to page source directory, but also related to engineering configuration.
There are four routing related in the SSR application:
-
Page source directory: usually placed in the root directory of src/pages/, less nesting;
-
app.json configuration: configure the access routes and corresponding page resources of each page in app.json;
-
SSR render function directory: where the rendering logic of the SSR page is located, under src/apis/render/, there may be nesting;
-
PAGE_NAME in the render function: the page resource path that the render function needs to use, and the server generates a document with content by executing this page file.
These four configurations affect each other, and the relevant documents are vague. For example, PAGE_NAME is not mentioned in the official documents, but only a sentence is commented in the initialization project: “The name of the page corresponds to the directory name under pages by default”. But from a practical point of view, this parameter is very important, and it even directly determines whether the server can render successfully.
The following is my verification and understanding of these routing configurations.
? Source directory and routing configuration
In a multi-page application, the source code of different pages is written in the same directory under src/pages/
:
├── src │ ├── app.json # Routing and page configuration │ ├── components/ # Custom business components │ ├── apis/ # server code │ └── pages # page source directory │ ├── a page │ ├── b page | └── c page ├── build.json # project configuration ├── package.json └── tsconfig.json
When no routing configuration is performed, it is accessed through domain name/a.html
by default, that is, using the directory name (lowercase) of the page under pages.
If you need to customize routing, modify the configuration in app.json, such as the following two page configurations:
{ "routes": [ { "name": "myhome", "source": "pages/Home/index" }, { "name": "pages/about", "source": "pages/About/index" } ] }
source
specifies the source code location of the page, and name
specifies the page route, so that we can pass domain name/myhome.html
, domain name/pages /about.html
visits the page.
The storage path of the build result reads the name
configuration:
└──build └── web # The construction result of csr resources is placed in the web directory ├── myhome.html/js/css └── pages └── about.html/js/css
? PAGE_NAME in the render function
The render function is the rendering logic we define on the nodejs server side when the user visits the page.
Let’s take a look at how PAGE_NAME
is used:
// page name, the default corresponds to the directory name under pages const PAGE_NAME = 'pages/index/index'; export default withController({ middleware: [ downgradeOnError(PAGE_NAME), // downgrade middleware ], }, async () => { const ctx = useContext(); // The business logic of the nodejs server //... // generate rendered document const ssrRenderer = await useSSRRenderer(PAGE_NAME); await ssrRenderer.renderWithContext(ctx); });
PAGE_NAME
is passed to useSSRRenderer
to generate the SSR document.
From the source code of useSSRRenderer
, we can see how PAGE_NAME
is consumed:
Inside the function, use PAGE_NAME
to stitch together the path after the page code is built, then find the corresponding file from this path and return it to the ssrRender
object, and finally execute it to generate a document.
So where is the page code placed after it is built? Take a look at the build results under the SSR project:
└──build └── client # client resource directory | └── web # csr resources are still in the web directory │ ├── myhome.html/js/css | └── pages | └── about.html/js/css | └── node # server resource directory ├── myhome.js # Node only generates js files └── pages └── about.js
The directory structure of node resources is configured according to name
in app.json.
Therefore, the value of PAGE_NAME is not the so-called “default corresponding to the directory name under pages”, but the construction directory corresponding to the page resource, that is, the page name configuration in app.json.
? Directory path to the render function
Let me talk about the conclusion first, the directory path of the render function directly determines the access route of the SSR link.
Take the following structure as an example:
└── src └── apis # client resource directory └── render # csr resources are still in the web directory ├── myhome.ts └── pages └── child └── about.ts
The project generates two SSR links: ssr domain name/myhome
, ssr domain name/pages/child/about
. It can be seen that the directory path of the render function is its access route. Since the SSR link accesses a service, not a document resource, the link does not end in .html
.
The names of the render file and the pages directory are kept the same for ease of understanding. According to the introduction to PAGE_NAME, we know that which page resource is used for server-side rendering has nothing to do with the name of the render file. It doesn’t matter if you name it abcd if the business requires it.
Therefore, there are almost no restrictions on the directory path of the render function, except in the following case.
When starting locally, the application will open the first generated link in the browser by default, and the generated link here is determined by app.json. If there is no corresponding resource in the directory path corresponding to your render, and the browser automatically opens the link for you, the server will report an error, or even disconnect directly. So the best practice for the render function is to be consistent with the name configuration of the corresponding page in app.json.
? Summary
To sum up, the relationship between relevant paths and page routing in SSR applications is shown in the following figure:
-
The pages page resource path determines the source configuration in app.json
-
The name configuration of app.json determines the location of the built product (CSR/SSR products all rely on this value), CSR access route
-
The path of the render function file determines the SSR access route
-
The PAGE_NAME in the render function determines the page resource used by the function, which needs to be consistent with the name value configuration of the corresponding page
technical problem
? Cloud build questions
After figuring out the above routing problem, the migration from CSR application routing to SSR application was quickly completed, the page was successfully launched locally, and the functions of the SSR page and CSR page were successfully verified.
But the accident still happened: when the pre-release cloud was built, the familiar window error appeared-is the environment wrong?
Checked the diff, and deleted all the objects with window in the transformation, and tried again, and the error still persisted, and continued to delete and delete until the change was deleted in pieces, and the error still existed.
Recalling that, jsdom was introduced to simulate the server environment. Even if the simulation is not good enough, the window will not exist, not to mention the success of local development and construction.
This error seems to have executed the page code during the build, so the problem was submitted to the architecture team.
Soon, the students in the architecture group confirmed the existence of the problem and gave a solution: a builder that has not been officially released.
? Manually simulate the code execution environment
While testing pre-issued SSR links, a new environmental issue emerged.
After locating the problem, I found that it is a pot of debugging plug-ins. Sort out its execution logic, probably like this:
window.__mito_result = 'something'; // define variable, mount on window console.log(__mito_result); // When using variables, window is not passed
The variable is hung under the window, but it is not accessed through the window. This method of directly reading the undefined variable will be found from the globalThis of the current environment at runtime. The globalThis on the node side is not the window, so the variable is not found, and the report not define
is wrong.
There are several solutions to this problem:
-
Modify the timing of plug-in execution
-
Modify plug-in injection timing: Because the server only needs to generate document content, this plug-in is irrelevant to the document and does not need to be injected into the pre-release resource
-
Environmental Simulation: use framework capabilities to simulate specific variables
Because this plugin will only be injected into the pre-release files and will not affect the officially released files. Therefore, it feels a bit heavy to modify the plug-in or function implementation. On the whole, the environmental simulation scheme was selected.
The integrated application simulates user-defined environment variables on the framework side with the following steps:
// The first step, configure mockEnvBrowser in build.json // build.json { "web": { "ssr": { "mockBrowserEnv": true }, }, } // The second step is to pass parameters in the render function // src/apis/render/render-function-path.js export default withController({ middleware: [ downgradeOnError(PAGE_NAME), phaIntercept(PAGE_NAME), ], }, async () => { // . . . const ssrRenderer = await useSSRRenderer(PAGE_NAME, { mockBrowserEnv: true, // need to be configured as true again globalVariableNameList: [ // List of variable names to be simulated '__mito_data', '__mito_result' ], // It is not necessary to implement the above variable, then the value of the above variable is undefined during execution browserEnv: {}, // The corresponding implementation of the variable to be simulated }); // . . . }
Here, by the way, I have studied the implementation of environment variables on the framework side, which is quite interesting. In the source code of useSSRRenderer, if you find that mockBrowserEnv: true
is configured, you will go to the following logic, the core of which is the string construction function:
Strip off its execution core logic:
// Define an execution function function mockEnvFn (...globalVariableNameList) { // define parameters execute('page. js'); // The page is in the context of the mockEnvFn function, and the window needs to be used in the page logic, which can be obtained from the parameter of the function } // execute the function mockEvnFn(globalVariableList); // pass in actual parameters
The framework layer wraps an outer function for the page function, defines the formal parameter list for the outer function, and then executes the outer function, so that the page function is in the context of the formal parameter, thereby realizing the environment simulation.
? Media/script tag injection
In the app.json of the CSR, there are metas attributes and scripts attributes, which can insert some media attributes and related tool library scripts before the page code is executed into the document. However, in SSR mode, these two attributes will not take effect and need to be rewritten into documents. One difference from the metas tag is that the js execution script is best placed in the dangerouslySetInnerHTML attribute, and the execution location is above the component and below the
In the actual build product, the CSR link takes the metas/scripts configuration in app.json, and the SSR link takes the tag in the document.
It should be noted that when the SSR link is downgraded to CSR mode, metas/scripts takes the configuration in app.json. Therefore, even if the SSR application does not need to deliver the CSR link, it should also maintain metas/scripts in two places at the same time, otherwise the performance may be inconsistent after the SSR is downgraded.
business problem
In the R&D platform, a warehouse can only access one application, and the access link generated by the application is tightly coupled with the application name. To make the CSR link released after closing the warehouse different from the CSR link of the original application, it is necessary to perform functional regression on the test side and link replacement on the business side.
? Testing issues due to resource access features
Due to the feature that the SSR application is not built when it is released, the documents of daily and pre-release builds will be used as online, and the resource access addresses in the documents are also typed online by default (the links to access resources are all g.alicdn.com) . However, the release of resources has not changed (resources are at dev.alicdn.com when pre-release, and resources are at g.alicdn.com when online), which leads to the fact that online resources cannot be obtained when pre-release documents are loaded.
The SSR server can change the resource links in the build document by dynamically setting webpack_public_path:
The pre-published link can normally access the resources under the pre-published CDN.
Modifying __webpack_public_path__
essentially means that SSR gets the runtime configuration and dynamically replaces the resource link when accessing. In CSR mode, the document is generated after the build, and there is no way to dynamically change the resource link at runtime.
In order to test that students can work normally, it is necessary to redirect the resource path through resource proxy when accessing the webpage.
? Business details link replacement issue
As for how to smoothly replace online links in the business domain, this is related to the specific situation of each business.
Taking car dealers as an example, we not only have access to basic link domains such as search, venues, stores, orders, and interactions, but also have shopping guides for car sites such as applets, and commercial clues for foreign capital retention. It is necessary to launch different links according to different scenarios, and it needs to be promoted separately.
Summarize
The SSR transformation has been done so far, and a lot of various sharing has been done. Engineers from other teams once asked me, why have I never encountered any of these problems you encountered?
After my own review, I concluded that it was originally a transformation project, and I needed to dance in shackles. The original project has been done for more than a year, and its complexity is relatively high. In addition, the diversity of business scenarios makes us have to maintain CSR and SSR links at the same time, which is also a responsibility that most SSR applications do not have to bear.
These three reasons make me have to explore the road that few people take. Although it is tortuous, fortunately it has gone through, and this is also thanks to the support of the big brothers of the architecture team.
In addition, there are some other experiences. In the process of promoting a technology to be implemented within the team, a large part of the factors are not at the technical level, but in the positioning of requirements, coordination of resources, and implementation of business. If these non-technical problems can be solved in the front, it will only be of great benefit to the implementation of technology.
team introduction
We are the automotive technology team of Alibaba’s Big Taobao Technology. We are a department integrating R&D, data, and algorithms. We use the Internet + digital to vertically integrate the automotive industry to create the ultimate experience for consumers to watch, buy, and maintain cars online. Here, you will come into contact with the core technology of new retail, transaction, supply chain, settlement, position operation, etc. Here, the team atmosphere is harmonious, the business is developing rapidly, and there are many technical challenges, allowing you to have business thinking and technical depth. On the road of rapid development of Ali Automobile, we look forward to your joining!
¤ Further reading ¤
3DXR Technology | Terminal Technology | Audio and Video Technology
Server Technology | Technical Quality | Data Algorithms
The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Java skill treeHomepageOverview 118238 people are studying systematically