Under the micro-frontend (Qiankun) project, how to use dllplugin to package and extract public dependencies and the problems encountered

foreword

For large-scale projects, it is often necessary to extract the dependencies of the public part to avoid repeated loading of dependencies. When extracting the public part of the plug-in, we can consider using externals [the use of externals of Webpack.note].
But the premise of externals is that the dependency must have a cdn or find its corresponding js file, for example: jQuery.min.js and the like, that is to say, these dependent plug-ins must support the umd format.
The dll plug-in can help us directly package the installed dependencies in node_module, combined with the add-asset-html-webpack-plugin plug-in to help us insert the generated packaged js file into html (doesn't need us like externals Then manually write the script to import)

1. Install the required plug-ins

three plugins

  • webpack-cli // The general project has been installed
  • add-asset-html-webpack-plugin // Used to dynamically insert dependencies into html through script tags
  • clean-webpack-plugin // empty the folder
npm install webpack-cli@^3.2.3 add-asset-html-webpack-plugin@^3.1.3 clean-webpack-plugin@^1.0.1 --dev

2. Write dll configuration related files

In the root directory (the path can be set by yourself), create a new webpack.dll.conf.js file

// webpack.dll.conf.js

// 引入依赖
const path = require('path');
const webpack = require('webpack');
const {
    
     CleanWebpackPlugin } = require('clean-webpack-plugin'); // 清空文件用的
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // 压缩代码用的

// 具体配置
module.exports = {
    
    
  mode: 'production', // 告诉 webpack 当前环境为生产环境
  // 入口 dll 自定义模块名字: [依赖1,依赖2,...] 
  entry: {
    
    
    vue: ['vue', 'vue-router', 'vuex'], // 打包 vue、vue-router、vuex依赖打包到一个叫 vue 的dll模块中去
    elementui: ['element-ui'],
    vendor: ['axios']
  },
  // 出口
  output: {
    
    
    filename: 'dll.[name].js', // 其中[name]就是entry中的dll模块名字,因此filename就是dll.vue.js
    path: path.resolve(__dirname, './dll/js'), // 输出打包的依赖文件到dll/js文件夹中
    library: '[name]_library'// 暴露出的全局变量名,用于给 manifest 映射 
  },
  plugins: [
    // 重新打包时,清除之前打包的dll文件
    new CleanWebpackPlugin({
    
    
      cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, './dll/**/*')] // ** 代表文件夹, * 代表文件
    }),
    // 生成 manifest.json 描述动态链接库包含了哪些内容
    new webpack.DllPlugin({
    
    
      // 暴露出的dll的函数名;此处需要和 output.library 的值一致
      // 输出的manifest.json中的name值
      name: '[name]_library',
      context: __dirname, // 在项目主要的配置中需要和这保持一致
      // path 指定manifest.json文件的输出路径
      path: path.resolve(__dirname, './dll/[name]-manifest.json'),  // DllReferencePlugin使用该json文件来做映射依赖。(这个文件会告诉我们的哪些文件已经提取打包好了)
    }),
    new BundleAnalyzerPlugin(),// 压缩
  ]
}

3. Package and generate dll

Add the following command to package.json

"scripts": {
    
    
    "build:dll": "webpack --config webpack.dll.config.js"
}

After running this command, the configuration of the dll in the second step will be executed to generate the dll
insert image description here

4. Ignore compiled files in project main configuration

In order to save compilation time, we need to tell webpack that the public library dependencies have been compiled to reduce the compilation time of webpack for the public library. Find vue.config.js in the project root directory (if not, create a new one), the configuration is as follows:

//  vue.config.js
const webpack = require("webpack");

module.exports = {
    
    
      ...
      configureWebpack: {
    
    
         ['vue', 'elementui', 'vendor'].map(item => 
            config.plugins.push(
            new webpack.DllReferencePlugin({
    
    
                context: __dirname,
                manifest: require(path.resolve(__dirname, `./public/dll/${
      
      item}-manifest.json`))
              }),
          )
      );
     }
 }

5. Reference the generated dll.js file in index.html

After the above configuration, the public library is extracted, and the compilation speed is faster, but if the generated dll file is not referenced, the dependencies of the project are not actually imported, and an error will be reported.
You can open public/index.html and insert script tags manually.

<script src="./dll/js/vendor.dll.js"></script>

At this point, the extraction of the public library is completed, but I always feel that the last manual insertion of the script is not elegant. The following describes how to automatically import the generated dll file.
Open vue.config.js under configureWebpack plugins configuration, continue to configure add-asset-html-webpack-plugin based on configureWebpack in step 4

// vue.config.js 
// 已经引入所需依赖
const path = require('path')
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
    
    
      ...
      configureWebpack: {
    
    
         ['vue', 'elementui', 'vendor'].map(item => 
            config.plugins.push(
            new webpack.DllReferencePlugin({
    
    
                context: __dirname,
                manifest: require(path.resolve(__dirname, `./public/dll/${
      
      item}-manifest.json`))
              }),
          )
      );
      config.plugins.push(
        new AddAssetHtmlPlugin({
    
    
             // 引用的dll.js文件位置
             filepath: path.resolve(__dirname, './public/dll/js/*.js'),
             // dll 引用路径 对dll静态资源引用的位置
             publicPath: './dll/js/*.js'),
             // dll最终输出的目录 打包后具体在dist下的文件位置
             outputPath: './dll/js/*.js'),
             includeSourcemap: false
           })
      )
     }
 }

After configuring the fifth step, restart the project and refresh the browser page to see the three js references we just set.
insert image description here

6. Sub-applications use these public dependencies

Taking the micro-frontend as the project background, the five steps of the appeal are all configured in the main application, while in the sub-applications (applications that need to use public dependencies), our configuration is relatively simple.
Just add the configuration in the fourth step to the webpack configuration of the corresponding sub-application, tell webpack to ignore these dependencies, and map these dependencies according to the manifest.

//  vue.config.js

module.exports = {
    
    
      ...
      configureWebpack: {
    
    
         ['vue', 'elementui', 'vendor'].map(item => 
            config.plugins.push(
            new webpack.DllReferencePlugin({
    
    
                context: __dirname,
                // 注意路径别写错
                manifest: require(path.resolve(__dirname, `../../main/public/dll/${
      
      item}-manifest.json`))
              }),
          )
      );
     }
 }

7. The introduction of on-demand dependencies (whether or not it depends on the situation)

Taking ant-design-vue as an example, the general component library depends on the following as needed:

import Button from 'ant-design-vue/lib/button';
import 'ant-design-vue/dist/antd.css'; //css 样式就直接引入吧

It was originally required to import on demand, so we are not suitable to directly package the entire ant-design-vue into the dll module, so the entry in webpack.dll.conf.js needs to be modified to:

// 入口 dll模块名字: [依赖1,,依赖2,,...] 
entry: {
    
    
    vendor: ['ant-design-vue/lib/button']
},

Disadvantages of dllplugins:

Because the use of public dependencies means that all applications that use public dependencies must use the same version of dependencies. In fact, I think this is not a shortcoming. It is a mandatory version of dependencies. After all, when there are many projects, the versions of dependencies must be unified, so When developing sub-applications independently, the version numbers must be consistent when installing public dependencies. For example: echarts dependency, the main application is 5.1.0 , and the sub-application is 4.1.0 , because of the different reference methods of echarts with different versions, the dependent sub-application cannot refer to the public dependency of the main application and reports an error, so that the sub-application cannot start up.

Problems encountered in the process:

1. When sub-applications use public dependencies, sometimes they report some errors inexplicably, although I don't know exactly what's going on.
For example, when highcharts is used (the references to highcharts are the same), the use of application a is normal, but application b will report a strange error, and application c is even more outrageous, and the sub-application cannot be started directly. . .
The dll dependencies of these three references are consistent:
insert image description here
the error report of application b:
insert image description here
the error report of application c:
insert image description here
after many repeated attempts, through search and exclusion, it was found that application b only modified the order of dll modules, and simply put highcharts Move to first, apply b and everything works fine! (What the hell!!!)
insert image description here
Then use c to try to change the order, move highcharts to the first place, and it's normal. . .

2. Citing the antd component library, there is a problem with the select all function of the table. Under normal circumstances, the use of the page form is normal, but the form is in a pop-up window, click to select all, and it is possible to print the data, that is to say, there is indeed a select all, but the class name of checkd is not added, It looks like not all are selected. And I remove the sub-application's antd reference to the dll, and use the antd in the sub-application node_module instead, so there is no problem, so this problem is indeed related to the packaging of the dll module. (The antd of the main application is 1.7.5, and the sub-application is 1.7.2)
insert image description here
In order to confirm whether it has anything to do with the version of antd, I upgraded the antd of the sub-application to 1.7.5, and then used this 1.7.5 dependency, The result is normal (that means it has nothing to do with version 1.7.5?). But later I changed the sub-application back to use dll dependencies, and changed the version of the antd component library of the main application to version 1.7.2, and then repacked the dll (every time I repackage the dll, I will restart the service of the sub-application), The result solves the problem. So it's hard to say whether the problem is related to 1.7.5 or not.

3. The project size does not change after using dll packaging.
In fact, when the sub-application uses the dll to ignore the extracted dependencies, the key point is to look at the xxx-manifest.json mapping file generated by the dll. If the mapping contains the dependencies used during sub-application development range (consistent versions of dependencies), the sub-application will ignore the dependencies extracted by the dll, thereby reducing the packaged volume. If the volume does not change after packaging, there is a high probability that it is a sub-application or an application that needs to ignore the dll packaging dependencies. Its dependencies are inconsistent with the mapping of packaging dependencies, so the public dependencies of my project are designed as follows:

-根目录
-主应用
    -node_modules // 主应用非公共的依赖
-子应用文件夹
    -子应用a
        -node_modules // 只安装子应用a非公共的依赖
    -子应用b
        -node_modules // 只安装子应用b非公共的依赖
-根目录下的node_modules // 公共依赖都npm安装在这    

According to the dependency search mechanism (first find the current file node_modules, and then search out layer by layer until the global npm dependency) and change non-public dependencies into devDependencies, and public dependencies into dependencies. This design is not only convenient for managing project dependencies (Guarantee that the development and use of public dependencies are the same), and only need npm install --only=dev to install the dependencies of devDependencies during development. Only when sub-applications are deployed independently, npm install is required to install all dependencies.

conclusion of issue:

  1. If you encounter problems related to resource loading, you can try to adjust the insertion order of the dll modules in the sub-application, that is, the order in the array in the sixth step above.
  2. For some application dependencies, if there are functional problems, try to keep the version consistent with the sub-application development and then repackage the dll dependencies.

Summarize:

externals and dll are necessary for large multi-application projects, because repeated dependencies can be extracted to greatly reduce the packaged volume and optimize project loading. At least in the micro-frontend project I developed, after testing , the volume is reduced by half. However, for a single application project, it may not be very effective. At most, the packaging time will be reduced, but the volume may not change much. After all, there will still be dependencies, and the size is difficult to change. However, it can be considered in the case of repeated use of dependencies. Use these two methods to optimize and reduce the size of the package.

When packaging the dll, remember that you don’t need to perform the packaging of the dll every time, because most of the modules packaged by the dll are third-party unchanged modules. Generally, you only need to package it once and store it in a fixed location (for example: under the public of the main application. ) and submit the code together, which can also reduce the packaging time of dependencies during run build, and the dll needs to be repackaged every time the dll's dependencies are modified, and after repackaging the dll, remember to restart the service for the sub-application, if the dll depends on Add and delete, also remember to add and delete the dll module of the sub-application.

Question Supplement

After the common dependencies are extracted, the sandbox of the qiankun framework is destroyed, resulting in mutual interference between global filters, components, mixins, etc. in different sub-applications. Problem Background
There
is a filter function byteToSize in sub-application A, but application A does not use it. Sub-application B also has this function, which is used in a table. Both applications register their filter function as a global filter and mount it under the Vue instance. After packaging and going online, I found that as long as I open sub-application B first, then open sub-application A, and then switch back to sub-application B, I will find that B uses A's byteToSize filter.

It seems that because the two byteToSize functions are global functions, the byteToSize of the sub-application A written later covers the byteToSize of the previous sub-application B, but isn’t the two applications sandbox isolated? Why does the following application code affect the previous application code? Is it possible that regardless of the main application or the sub-application, the registered plug-ins such as vue.use, filter, component, etc. are shared?

1. Verification and recurrence problems
If the vue of all applications is shared (considered to be the same), then the vue.use registration plug-in in the application has already registered multiple sides. In order to prove this point, I removed the application of a certain sub-application, such as the antd plug-in. After repackaging, I opened the page of this sub-application and found that everything is normal, that is to say, the antd plug-in of the ui component library in the sub-application is actually No need to register anymore. Of course, repeated registration of plug-ins of this style has no effect, but filters or global components in other problems are easy to cause coverage conflict pollution due to repeated use registration.
According to the results of the above analysis, the plug-ins registered later should overwrite the previous ones, so every time you click on a sub-application, shouldn’t you load your current filters, components, etc.? So I added the printing code to the main.js of each sub-application, and found that it will only print when the sub-application is loaded for the first time. That is to say, the unloading of sub-applications in the micro-frontend that I understood before is not to completely kill the sub-applications without any reservations, but to uninstall the vue instance, and the subsequent sub-applications are not loaded for the first time, and they are not even loaded. The code in main.js will be executed again, and the micro front-end sub-application of the same domain is really no different from a vue project (I can’t help but think, if it is not necessary to deploy front-end projects across domains, it seems that there is really no need to use Micro-frontend technology, after all, the Vue framework is already developed in a modular way. But the micro-frontend of the same domain is at least better than the modular development without micro-frontend, which is undeniable).
2. The cause of the problem
and when I remove the dllplugin configuration of the sub-application, repackage and view the system, the console will report

Uncaught TypeError: Cannot redefine property: $router

Error, because the main application uses the script tag to introduce vue-router, the vue.use of the sub-application can no longer rely on the vue-router in its own node_modules, and can only use the vue-router of the script tag of the main application, otherwise it will An error is reported because $router is repeatedly defined under the same vue.
Helpless, I can only cancel the configuration of the main application's public dependencies, restore the configuration before not using the public dependencies, and then find that the problem is gone after packaging. If you rely on the method introduced by the script tag of the main application, under the import-html-entry of Qiankun, it is impossible to isolate the js of the common dependencies of the sub-application and the main application, because after all, they coexist under the same window and are registered in the public dependencies. Global variables are shared, unlike iframe which has a strong JS sandbox isolation.
3. Solve the problem
In fact, it is nothing more than vue as a public dependency, which will cause applications to interfere with each other. If I completely remove the vue dependency from the public dependencies, will it be solved? But when I remove it, I found that the console will still report

Uncaught TypeError: Cannot redefine property: $router

mistake. This is very strange. So I tried to remove all public dependencies related to vue, and even tried to leave only one echarts public dependency, but I couldn't remove this error. Obviously, vue and vue-router are not introduced as script tags in index.html, but this error will still be reported! This error will not be reported only when the sub-application does not use DllReferencePlugin. It feels like DllReferencePlugin has done some special operation to expand the js context scope of different sub-applications to the whole system. Now it seems that this DllReferencePlugin seems to break the js sandbox of the qiankun framework.

In the end, the idea was solved in qiankun's GitHub question area:
insert image description here
in a word, either give up the extraction of public dependencies, or upgrade vue2.x to vue3.x. Vue3.x uses createApp to register through instances, so that there will be no global pollution. This is the best solution. As for modifying the name of the filter function to be unique, I think it is not a fundamental solution. The method name can be unique, but the global minxin has no name, and it will only be mixed in all pages of all applications, which is very bad.

Guess you like

Origin blog.csdn.net/weixin_43589827/article/details/118632092