picture


This article corresponds to the operating system and hbuilderx version, etc.

- -
operating system macOS
HbuilderX 3.4.18.20220630
vue 2.x
uView 1.x

1. Project Background

This is an h5 project nested in flutter. The outer shell is an app, and most of the pages inside are h5. The app directly uses webview to display h5 pages

2. Why use uniapp to develop h5?

Because we also need to develop WeChat applet, using uniapp, it can be cross-platform and save time

3. Project initialization

Project initialization is a project created using the HBuilderX visual interface, and there is no way to use the vue-cli command line.

The project created by the official IDE will bring trouble to the subsequent CI/CD construction and deployment of the project. Because it is not a project created by the command line, it is impossible to execute the command in linux. It can only be packaged locally, and then uploaded the packaged unpackagedirectory for deployment. Of course, there are many convenient places to use HBuilderX.

3.1 Conversion between uni-app HBuilderX project and vue-cli project

The HBuilderX project is converted into a vue-cli project. I see that there are pits in the comment area, and the project is large. It may not be able to solve the pits, so the cost will be high.

3.2 Can a project created by HBuilderX call the packaging command of hbuilderx?

That is to call the packaging function of hbuilderx through the command line

  • CLI releases uni-app to H5
  • cli configure environment variables

However, this is still not as convenient as the vue-cli command line at the beginning, and they are all packaged on local windows or mac, and cannot be packaged through shell scripts on linux. In the end, it can only be packaged locally and uploaded to the git repository. If there is an elegant solution here, please share it in the comment area.

3.3 Can a project created by HBuilderX use npm?

The official documentation clearly says, yes

4. Production packaging removalconsole

official documentation

// vue.config.js

module.exports = {
  chainWebpack(config) => {
    // 发行或运行时启用了压缩时会生效
    config.optimization.minimizer("terser").tap((args) => {
      const compress = args[0].terserOptions.compress;
      // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
      compress.drop_console = true;
      compress.pure_funcs = [
        "__f__"// App 平台 vue 移除日志代码
        // 'console.debug' // 可移除指定的 console 方法
      ];
      return args;
    });
  },
};

5. How to distinguish the environment when the project created by HBuilderX is packaged?

That means, how to npm run build:devpackage like the test environment that npm run build:prodexecutes the execution is to package the production environment.

official documentation

Add configuration to custom distribution-package.json

{
  "uni-app": {
    "scripts": {
      "h5-prod": {
        "title""h5:prod",
        "browser""",
        "env": {
          "UNI_PLATFORM""h5",
          "NODE_ENV""production",
          "DEPLOY_ENV""prod"
        },
        "define": {
          "CUSTOM-CONST"true
        }
      },
      "h5-dev": {
        "title""h5:dev",
        "browser""",
        "env": {
          "UNI_PLATFORM""h5",
          "NODE_ENV""development",
          "DEPLOY_ENV""dev"
        },
        "define": {
          "CUSTOM-CONST"true
        }
      }
    }
  }
}

After adding, for local development, you can 运行 - h5:devstart the project locally in the menu, 发行 - 自定义发行 - h5:devpackage the test environment, and of course click 发行 - 自定义发行 - h5:prodto package the production environment.

vue.config.js6. How to get custom processvariables root directory ?

Because we want to keep the debugger logs in the code when the test environment is packaged, remove the log printing when the production environment is packaged.

Like process.env.DEPLOY_ENVthe variable defined above, it can be obtained normally in business code, but cannot be obtained in the configuration file vue.config.js.

  1. Installdotenv
npm i dotenv -D
  1. Revisevue.config.js
require("dotenv").config();

module.exports = {
  chainWebpack(config) {
    config.when(process.env.NODE_ENV === "production", (config) => {
      // 我们可以拿到process.env.UNI_SCRIPT这个变量来进行操作
      if (process.env.UNI_SCRIPT === "h5-prod") {
        // https://uniapp.dcloud.io/collocation/vue-config.html
        // 发行或运行时启用了压缩时会生效
        config.optimization.minimizer("terser").tap((args) => {
          const compress = args[0].terserOptions.compress;
          compress.drop_console = true;
          compress.pure_funcs = [
            "__f__"// App 平台 vue 移除日志代码
          ];
          return args;
        });
      }
    });
  },
};

7. The file name is too long after packaging h5

The project is slightly larger, the page directory is nested deeper, and the packaged file name is huge, even more than 100 characters long. If the file name is too long, it will bring disgusting problems, such as being intercepted by nginx, unable to get the file content, causing the page to hang.

// vue.config.js
module.exports = {
  chainWebpack(config) => {
    config.when(process.env.NODE_ENV === "production", (config) => {
      config.output
        .filename("static/js/[name]-[contenthash].js")
        .chunkFilename("static/js/[id]-[chunkhash].js");
    });

    config.optimization.splitChunks({
      namefunction (module, chunks, cacheGroupKey{
        const moduleFileName = module
          .identifier()
          .split("/")
          .reduceRight((item) => item);
        const allChunksNames = chunks.map((item) => item.name).join("~");
        return `${moduleFileName}`;
      },
    });
  },
};

8. After packaging h5, the chunk-vendorsfile size is too large, and the page is opened slowly after deployment

Fortunately, vue.config.jsthis file was exposed and handed over to the developer, otherwise it would be a big deal to deal with.

  1. First enable manifest.jsonthe configuration in the configuration file tree-shaking, "shake out" those redundant code

  2. remove prefetch plugin

// 这个视自己的项目而定
// vue.config.js
module.exports = {
  chainWebpack(config) => {
    // 移除 prefetch 插件
    // https://cli.vuejs.org/zh/guide/html-and-static-assets.html#prefetch
    config.plugins.delete("prefetch");
  },
};
  1. Pack for gz or br compression
  • Installcompression-webpack-plugin

There is a problem here, only webpack5.x version is supported after 7.x version, here is webpack4.x, only 6.x version can be used
https://github.com/webpack-contrib/compression-webpack-plugin/releases? page=2

7.0.0 (2020-12-02)

BREAKING CHANGES
minimum supported webpack version is ^5.1.0

Install

npm i [email protected] -D
  • Modify vue.config.js
// vue.config.js
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  configureWebpack(config) => {
    if (process.env.NODE_ENV === "production") {
      config.plugins.push(
        new CompressionPlugin({
          filename"[path][base].gz",
          algorithm"gzip",
          test/\.(js|css|html)$/,
          threshold10240,
          minRatio0.8,
          deleteOriginalAssetsfalse,
        })
      );
    }
  },
};
  • At the same time, nginx turns on gzip
server {
gzip on; #开启gzip
# gzip_buffers 32 4k; #设置压缩所需要的缓冲区大小,以4k为单位,如果文件为32k则申请32*4k的缓冲区
gzip_comp_level 6; #gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
gzip_min_length 4000; #gizp压缩起点,文件大于4k才进行压缩
gzip_vary on; # 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_static on; #nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
# gzip_types text/xml text/javascript application/javascript text/css text/plain application/json application/x-javascript; # 进行压缩的文件类型
}
  1. Split chunk-vendors by combining webpack-bundle-analyzerthe generated module analysis graph
  • 4.1 Installation
npm i webpack-bundle-analyzer -D
  • 4.2 Introductionwebpack-bundle-analyzer
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  configureWebpack(config) => {
    config.plugins.push(new BundleAnalyzerPlugin());
  },
};

As shown in the figure below, the composition of chunk-vendors is to package all those public dependencies together, and the file size reaches more than 1M, which seriously affects the page loading speed. After gzip compression in the generation environment, the home page loading also takes about 10s. , shameful. (The split here depends on the specific project)

picture

  1. Need to @dcloudiosplit (658kb) into "uni-h5" part and "vue-cli-plugin-uni" part
  2. Remove core-js(282kb) separately
  3. Remove node-rsa(112kb) separately
  4. Remove bn.js(88kb) separately
  5. ...

The entry index file has also reached almost 350kb, and there is a large pages.json file in it

picture

Remove pages.json(290kb) separately

Look at chunk-vendors on the browser, there is also a buffermodule

picture

Take the buffermodule apart

  • 4.3 optimization.splitChunksSplitting modules

here is webpack4

// vue.config.js
module.exports = {
  chainWebpack(config) => {
    config.optimization.splitChunks({
      chunks"all"// 必须三选一: "initial"(同步包) | "all"(推荐,同步或异步包) | "async" (默认就是async,异步包)
      automaticNameDelimiter"~"// 打包分隔符
      namefunction (module, chunks, cacheGroupKey{
        const moduleFileName = module
          .identifier()
          .split("/")
          .reduceRight((item) => item);
        const allChunksNames = chunks.map((item) => item.name).join("~");
        return `${moduleFileName}`;
      },
      cacheGroups: {
        vendors: {
          name"chunk-vendors",
          chunks"initial",
          reuseExistingChunktrue,
          enforcetrue// 遇到重复包直接引用,不重新打包
          priority0// 打包优先级权重值
          // minChunks: 1,  // 引用超过一次直接打包到chunk-buffer中
          minSize30000,
        },
        pages: {
          name"chunk-pages",
          test/pages\.json$/,
          chunks"all",
          reuseExistingChunktrue,
          enforcetrue,
          priority90,
          minChunks1,
          minSize10000,
        },
        "node-rsa": {
          name"chunk-node-rsa",
          test/node-rsa/,
          chunks"all",
          reuseExistingChunktrue,
          enforcetrue,
          priority70,
        },
        buffer: {
          name"chunk-buffer",
          test/buffer|is-buffer/,
          chunks"all",
          reuseExistingChunktrue,
          priority90,
          minChunks1,
          minSize15000,
          enforcetrue,
        },
        "core-js": {
          name"chunk-core-js",
          test/core-js/,
          chunks"initial",
          reuseExistingChunktrue,
          enforcetrue,
          priority70,
        },
        "uni-h5": {
          name"chunk-uni-h5",
          test/uni-h5/,
          chunks"initial",
          reuseExistingChunktrue,
          enforcetrue,
          priority80,
        },
        vue: {
          name"chunk-vue",
          test/vue-cli-plugin-uni/,
          chunks"initial",
          reuseExistingChunktrue,
          enforcetrue,
          priority85,
        },
        bn: {
          name"chunk-bn",
          test/bn/,
          chunks"initial",
          reuseExistingChunktrue,
          enforcetrue,
          priority70,
        },
      },
    });

    // 提取公共的runtime
    config.optimization.runtimeChunk("single");
  },
};
  • 4.4 Split chunks are not index.htmlautomatically introduced in

The packaging was successful without any errors, but when running the project, the page was blank without any UI display. The problem is that the split small chunks are not automatically introduced in index.html . We can start a static service locally in the packaged unpackage directory, manually import those chunks, refresh the page, and the page ui will be displayed. span

So we need to find the configuration of html-webpack-plugin, add the chunks we split out separately to the chunksconfiguration

// vue.config.js
function changeHtmlWebpackPluginChunks(config{
  const plugins = config.plugins;
  const chunkArr = [
    "runtime",
    "chunk-vue",
    "chunk-uni-h5",
    "chunk-core-js",
    "chunk-node-rsa",
    "chunk-buffer",
    "chunk-bn",
    "chunk-pages",
  ];

  plugins.forEach((item, index) => {
    // 为HtmlWebpackPlugin
    if (
      item.options &&
      Object.prototype.toString.call(item.options) === "[object Object]" &&
      Object.hasOwnProperty.call(item.options, "filename") &&
      Object.hasOwnProperty.call(item.options, "chunks") &&
      item.options.filename === "index.html"
    ) {
      const oldChunks = item.options.chunks || [];
      // 修改chunks引入
      item.options.chunks = [...chunkArr, ...oldChunks];
    }
  });
}

module.exports = {
  configureWebpack(config) => {
    if (process.env.NODE_ENV === "production") {
      changeHtmlWebpackPluginChunks(config);
    }
  },
};

The picture below shows chunk-vendors after splitting. Compared with the previous ones, those large modules have been split, the file size has changed from more than 1M to 320kb, and the loading time of the home page has also changed from more than 10s to about 3s.

picture

  • uview-ui4.5 Some components report errors after splitting vendors

uview-uiThe ui framework is used in the project

Repackage, start the project again locally, and the browser console finds the following error

picture

Go to the unpackage directory and see that there are still u-icon.vueand unpackaged u-line.vue!

Then extract these two files separately and package them as chunks, and start the project after packaging again. These two components do not report errors, but other components report errors. There are more "u-xx.vue" components in the unpackage directory. What is this? Happening?

The guess is that the easycom component mode when the uview-ui framework was introduced caused the ghost. I tried to add a custom customconfiguration in easycom and write these two components in it, but it didn't work. The page does not report an error, but there is a problem with the style. . .

Or delete the easycom configuration directly, start the project, there is a problem with the page style. . .

In the end splitChunks, uview-ui is extracted separately as a chunk, so the problem is solved, but at the time of entry, an extracted uview-ui must be loaded (all uview-ui components used in the project, not the entire uview -ui). Before splitting, each page only needs to load the components it needs. Now the components of all pages are extracted, which means that each page is loaded with redundant uview-ui components. Fortunately, the final page load speed is faster than before.

If you know how to deal with it, or if you encounter the same problem, please share how to deal with it.

  • 4.6 After splitting into small chunks, there are more js requests on the page

    picture

There are more js requests on the page, which also has a certain impact on the performance of the page, so it is divided here, depending on the specific project.

9. The project created by hbuilderx, h5 continuous deployment

It depends on the internal situation of the company. I have ready-made nginx on the server, and I don't need to package the docker image myself.

  1. .gitlab.yml

SSH_USERNAME, SSH_HOST, SSH_PASSWORD These three variables are written in the Setting->CI/CD->Variablesproject

stages:
  - build

job_build:
  stage: build
  script:
    # - sudo docker image build -t demo_h5 .
    # - sudo docker tag demo_h5 registry.cn-hangzhou.aliyuncs.com/test-blog/demo_h5:latest
    # - sudo docker login --username=$ALIYUN_DOCKER_NAME registry.cn-hangzhou.aliyuncs.com --password=$ALIYUN_DOCKER_PASSWORD
    # - sudo docker push registry.cn-hangzhou.aliyuncs.com/test-blog/demo_h5:latest
    # - sudo docker logout
    - if yum list installed | grep 'sshpass'; then echo yes; else yum -y install sshpass;fi
    - sh deploy.sh $SSH_USERNAME $SSH_HOST $SSH_PASSWORD
  tags:
    - xxxx(公司内部gitlab注册一个runner,或者用一个公共runner)
  only:
    - main
  1. deploy.sh

Upload the packaged file to the specified directory on the server, and then the page can be accessed 

Why should the packaged code be compressed? Because the number of packaged files is large, if they are transmitted one by one, it will be very slow, so it needs to be compressed and converted into one file for transmission.

#!/bin/bash

echo "ssh deploy start==>"
# echo "$3" ssh "$1"@"$2" --password-stdin

username=$1
host=$2
password=$3
# 打包目录
dir="./unpackage/dist/build/h5"
# 服务器目录
target="/home/demo_h5/nginx/html/app-web"

# 上传本地文件到服务器
# scp /path/filename username@servername:/path
# 上传目录到服务器
# scp -r local_dir username@servername:remote_dir

# sshpass -p ${password} scp ${dir}/index.html ${username}@${host}:/home/demo_h5/nginx/html/app-web
# sshpass -p ${password} scp -r ${dir}/static ${username}@${host}:/home/demo_h5/nginx/html/app-web

# cd到指定目录
cd ${dir}
# 压缩所有文件并重命名
zip -r -q -o build.zip ./
# 通过sshpass上传压缩文件到指定目录
sshpass -p ${password} scp ./build.zip ${username}@${host}:${target}
sleep 2

# 登录远程服务器
# cd 到指定目录
# unzip 解压到指定目录
#   -o 不必先询问用户,unzip执行后覆盖原有文件。
#   -d 解压到指定目录
# 删除压缩文件

sshpass -p ${password} ssh ${username}@${host} "cd /home/demo_h5/nginx/html/app-web && pwd && unzip -o -q ./build.zip -d ./ && rm -rf ./build.zip"

echo "ssh deploy end==>"

  1. git commit code

Not surprisingly, the code is automatically deployed to the corresponding location, and the browser can access it

$ if yum list installed | grep 'sshpass'; then echo yes; else yum -y install sshpass;fi
Repository base is listed more than once in the configuration
Repository updates is listed more than once in the configuration
Repository extras is listed more than once in the configuration
Repository centosplus is listed more than once in the configuration
Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
sshpass.x86_64 1.06-2.el7 @extras
yes
$ sh deploy.sh $SSH_USERNAME $SSH_HOST $SSH_PASSWORD
ssh deploy start==>
/home/demo_h5/nginx/html/app-web
ssh deploy end==>
Job succeeded

10. References

  1. uniapp official documentation
  2. webpack4 official documentation
  3. vue-cli official documentation