background

📢 Blog launch: SugarTurboS Blog

The team’s project A went through two years of requirements, and some problems were also exposed:

  • project-referencednpmA lot of packages, a lot of business code, there is a boulder application development trend. Some typical problems with megalithic applications are as follows:Low construction efficiency,Dev-server occupies a large memory footprint or even leaks memory,Maintenance costs have increased dramatically.
  • The main framework of the project is expensive to upgrade and compatible with old code.
  • Some of the businesses in the project are almost no longer iterated, but each version is still packaged and built, every timenpmPackage versions may differ, leading to someHiding unknown errors.
  • This project was merged from two different projects before. There were two ways of code style and different technical solutions were introduced to solve similar problems, which resulted in high maintenance cost and poor readability for newcomers.

The road to solve

Why micro front end

For the difference between micro front end and IFrame solution, why to use micro front end this question, here is no longer cumbersome, there is an article in Qiankun has said very good, interested can go to see. why not iframe

Why do we chooseqiankun

  • qiankunAccess to the project changes small, low cost.
  • The community is active and the author responds quickly to the issue.
  • The number of stars is relatively large
  • Ali’s own project is in use, stability is OK, that is, production is available (this point is actually official, we are not sure, as far as we feel the use, stability is guaranteed to some extent).

The road to reconstruct

This pit is not the one in Qiankun. Of course, the Qiankun framework has some flaws. Here mainly refers to the time of project reconstruction, encountered some pits and our solutions for your reference.

Two React pits

In the previous structure of the project, all packages were installed in node_modules at the root, and everything in the project pointed to a React. After reconstruction with Qiankun, we defined each sub-project as a relatively independent project (with independent package.json file and independent package management), but there would be some common components before the sub-project, which we put in the common folder at the same level as the sub-project, as shown in the figure below.

At this point, there is a problem: The child projects refer to node_modules in package.json, while the common projects refer to node_modules in package.json in the root directory. When a child project references the Common encapsulated React component, it will report that two React components are introduced at the same time, causing an error.

At first, the solution we came up with was to install all packages on node_modules in the root directory. However, the biggest problem of this scheme is that all subprojects must use the same version of React. In order to upgrade React in the later stage, all subprojects must be compatible. However, some subprojects are divided in order not to follow the upgrade iteration, which is contradictory.

Later, we switched to a solution where we pre-copied the common directory to the subproject when packaging, so that we could keep all React references. During the development, an additional monitoring service, watch common directory, will be started to automatically copy files to the subproject when the changes of files are monitored. The common directory of the subproject will have permission control, and only read and write operations can be performed, but no other operations can be performed. All references to common are mapped via @common. At development time, changes to the common content only need to be made in the root directory common, and the subprojects referenced by @common do not need to pay attention to the actual relationship between common and the directory structure of the subprojects.

Babel configures unreadable pits

Before refactoring, we had only one Babel configuration. After refactoring, our directory structure is a typical Monorepo structure. Our project is built using babel7, so common can’t find the configuration file.

At first, we’ll use.babelrc.json as we did with Babel6. The root directory and subproject have a. Babelrc. json file respectively. The biggest disadvantage of this is that the configuration of the two.

Then use the.babelrc.json subproject to reuse the.babelrc.json configuration of the root directory via the extends configuration.

Later, I found that some Babel configurations need to configure paths, while JSON can only configure relative paths, so I changed to JS configuration.

Some NPM packages referenced in our project were not transferred to ES6. We had to use Webpack to convert these packages with additional Babel. However, the Babel configuration of the discovery project does not apply to the NPM package. It turns out that after babel7,.babelrc doesn’t work on the node_modules package and must be replaced with babel.config.js.

So that’s our final configuration of Babel.

If babel-loader is upward, Babel is upward. If Babel is upward, Babel is upward. If Babel is upward, Babel is upward.

Babel6.x is different from Babel7, and Babel’s configuration for monorepo is the most detailed.

communication

Qiankun only provides global state management of initGlobalState(initializing a global state), onGlobalStateChange(listening for changes) and setGlobalState(updating State). Does not associate with the React state manager. What we need to do is bind global state bidirectional to the child application Redux.

// A deep comparison between state and globalState will cause the application to fall into an infinite loop.
const [state, setState] = useState({}) // Use state instead of redux for a simple demonstration.
let globalState = null
// Listen for globalState changes and update the state if any
actions.onGlobalStateChange((newGlobalState) = > {
  globalState = newGlobalState
  const diffState = getDiffState(globalState, state)
  if (diffState) setState(diffState)
})

// Listen for changes in state and update globalState if any
useEffect(() = > {
  const diffState = getDiffState(state, globalState)
  if (diffState) actions.setGlobalState(diffState)
}, [state])
Copy the code

Asynchronous loading

Since our project was loaded asynchronously with import from Webpack. After using Qiankun for reconstruction, the following problems were found:

Currently, it is in subapplication A and switches to subapplication B. In the loading process of asynchronous JS, it quickly switches back to application A. After the asynchronous JS loading of the child application B is completed, we switch back to the child application B and find that the content of the asynchronous JS loading of the child application is empty.

The reason for this problem is that after A->B->A process, the sandbox of child application B is removed, and asynchronous JS lacks an execution environment, resulting in the execution of asynchronous JS (window.webpackjsonp…). I can’t find it.

Currently no effective solution has been found, this may be a hidden pit in the framework, has raised the issue, we hope the big guys can help solve. The possible solution we have now come up with is to use loadMicroApp to manually load the child application.

Fetch differences for browsers

During the testing of the project, it was found that some browsers (currently known as a version of Sogou Browser) had compatibility problems. Later, it was found that the default fetch credentials of some browsers were not same-origin, which resulted in that some cookie headers were not brought, and the background permission authentication failed all the time.

The resolve is to call the Start of Qiankun to rewrite the fetch and set the credentials=same-origin to ensure browser compatibility.

start({
  fetch(. args) {
    const config = {
      credentials: 'same-origin',}if(! args[1]) args[1] = {}
    args[1] = {
      ...args[1],
      ...config,
    }
    returnfetch(... args) }, })Copy the code

conclusion

In fact, overall, access qiankun cost is relatively low. Most of the problems encountered were not directly caused by Qiankun, but caused by changes in project structure after reconstruction with Qiankun.

Optimize the development experience section

After the reconstruction of the project, some performance and development experience problems occurred due to the change of the overall structure. Here mainly say the influence is bigger two points.

The sub-applications cannot be hot updated due to heavy memory usage

1. Found by colleagues, the project is usedqiankunAfter refactoring, during local development, ifchrome toolOpen it for a long time, and the more times the page is refreshed,chromeMemory usage will become more and more serious. In theory, even if the application has a memory leak, refresh the page will be released, why is the memory increasing? Turns out, as long as you don’t open itchrome tool, memory is normal, refresh memory will come down. Also, if we use unrefactored branch validation, we won’t get more and more memory. As shown in figure:

2, sub-application content changes, is unable to hot update. At first I thought it waswebpackThe configuration is not caused by pairing. It turned out not to bewebpack, butqiankunThe use ofsingle-spaThe framework problem. Detailed visibleissue. The authors also offer a solution that allows you to reload child apps. But this violates the idea of updating parts of hot updates. Also, loading a child app isn’t much different than refreshing it, and the development experience sucks.

We discussed and found that there was no solution to the problem. There is only one way to avoid it, that is, when we usually develop, use sub-application routing to develop, so that we can avoid these two slightly painful problems. Of course, in some scenarios, if the main application does something with permissions, a separate child application must rewrite a set of permissions. Our current practice is to hang such modules in a public directory. We will continue to explore whether there is a better solution. If you have a better solution, please leave a message.

Monorepo project development command management

The project team’s partner joked that we only need a command line NPM run dev before development can be done. After the Qiankun refactoring, each sub-application started one service and qiankun also required one service. To run the whole thing, we need to open multiple command-line Windows and run them separately. It’s too much trouble. To solve this problem, nPM-run-all is introduced naturally. At the beginning, we also did this, but later we found that in the actual development process, sometimes little A only needs to develop child application A, and little B only needs to develop child application B, and everyone starts all of them, which is A waste of memory resources and not elegant. So how do you start the service you want with one command at a time? We ended up using the NPm-run-all Node API. Process the command line autonomously, and then use the API it provides to dynamically start the desired service. For example, NPM Start Main A will start qiankun’s service and A micro service.

// package.json
"scripts": {
  'start': 'node start.js'."start:main": "cd client/main && npm run dev"."start:A": "cd client/A && npm run dev"."start:B": "cd client/B && npm run dev"."start:C": "cd client/C && npm run dev",}Copy the code
// start.js
const runAll = require('npm-run-all')

function getApps() {
  // Find the parameters of the command line. If there are no parameters, start the Main and A services
  let apps = process.argv.filter((arg) = >
    ['Main'.'A'.'B'.'C'].some((name) = > name === arg)
  )
  if (apps.length <= 0) apps = ['Main'.'A']
  return apps
}

function getTasks() {
  let apps = getApps()
  let tasks = apps.map((app) = > `start:${app}`)
  return tasks
}

runAll(getTasks(), {
  parallel: true.// stdout: writable,
  // stderr: errWritable,
  // printLabel: true,
})
  .then((results) = > {
    console.log('done! ', results)
  })
  .catch((err) = > {
    console.log('failed! ', err)
  })
Copy the code

Public package

After refactoring, we found that there are some apps that use React, ANTd… If each sub-application is installed relying on an ANTD, it will waste resources to load and affect the user’s first screen waiting time. Qiankun did not offer one. We finally use the DLL way, the first of these public packages packed in advance, in the DLL to each sub-project. The DLL approach also has some disadvantages, because DLLS cannot be loaded on demand and can only reference the entire package, again. DLLS need to be loaded ahead of time, which can be particularly wasteful if the DLL packages things that are not used very often or in the first screen. So we generally only consider DLLS if the following conditions are met:

  • 1. Use for multiple projects
  • 2. High frequency of project use
  • 3, thenpmAlmost all of the functionality of the package is required

At the end

Finally, our thoughts. Whether the project needs to introduce Qiankun, we think the key is to know the benefits and costs after introducing Qiankun. Take our project as an example, because it is foreseeable that when the business gets bigger and bigger, it will definitely become a boulder app. At present, the cost of qiankun’s access may be higher than the benefit, but in the long run, the benefit is definitely higher than the cost, so we brought it into the project.

Finally, due to space limitations, many details are not shown here. If you are interested, you are welcome to communicate privately.

Without authorization, prohibit reprinting ~

More articles

  • SugarTurboS Blog
  • Redux source code interpretation and programming art
  • Talk about the front end design mode
  • Are you stuck with repeated requests for projects?
  • Basic analysis and scheme design of collaborative editing scenes