【前端】生产环境部署及性能调优

概述

前端工程使用vue-cli-service进行构建,生成dist静态资源目录,其中包括htmlcssjavascript以及其他项目中使用到的所有资源。

在生产环境中,我们通常使用Nginx开启访问服务,并定位其访问目录至dist目录下的index.html,以此来实现前端工程的访问。

不仅如此,为了使得前端发起请求时,可以正确访问到后端服务,也需要在nginx中配置相应的代理,使得访问过程在同域中进行,以达到Cookie共享的目的。

当然,服务部署的形式可以有多种,上述的部署方式也是较为常用的部署方式。

部署

  • 使用production模式进行打包
  • 使用dotenv-webpack插件启用process.env
  • 配置chainWebpack调整资源加载顺序
  • 使用thread-loader进行打包加速

性能调优

  • 使用compression-webpack-plugin插件进行压缩打包
  • 启用Nginxgzipgzip_static功能
  • 使用OSS加速静态资源访问(可选)

使用production模式进行打包

在package.json中添加执行脚本
{
  "scripts": {
    "build": "vue-cli-service build --mode production"
  }
}
执行打包命令
npm run build

使用dotenv-webpack插件启用process.env

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "dotenv-webpack": "1.7.0"
  }
}
npm install dotenv-webpack@1.7.0 --save-dev
在vue.config.js中添加配置
const Dotenv = require('dotenv-webpack');

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    plugins: [
      new Dotenv()
    ]
  }
};
.env加载顺序

使用不同模式,加载的文件不同。文件按照从上到下依次加载。

  • development

    • .env
    • .env.development
  • production

    • .env
    • .env.production

配置chainWebpack调整资源加载顺序

chainWebpack对资源加载顺序取决于name属性,而不是priority属性。如示例中的加载顺序为:chunk-a --> chunk-b --> chunk-c。

code>test属性决定其打包范围,但集合之间会自动去重。如chunk-a打包node_modules下所有内容,chunk-b打包node_modules/@kunlun下所有内容。因此在chunk-a中将不再包含node_modules/@kunlun中的内容。没有test属性将意味着打包其他内容。

通常情况下,我们希望第三方资源优先加载,@kunlun会覆盖第三方资源(如css样式在同级别选择器中优先级取决于加载顺序),项目中的资源优先级最高。

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  chainWebpack: (config) => {
    config.plugin('html').tap((args) => {
      args[0].chunksSortMode = (a, b) => {
        if (a.entry !== b.entry) {
          // make sure entry is loaded last so user CSS can override
          // vendor CSS
          return b.entry ? -1 : 1;
        } else {
          return 0;
        }
      };
      return args;
    });
    config.optimization.splitChunks({
      ...config.optimization.get('splitChunks'),
      cacheGroups: {
        libs: {
          name: 'chunk-a',
          test: /[\\/]node_modules[\\/]/,
          priority: 1,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        kunlun: {
          name: 'chunk-b',
          test: /[\\/]node_modules[\\/]@kunlun[\\/]/,
          priority: 2,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        common: {
          name: 'chunk-c',
          minChunks: 2,
          priority: 3,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    });
  }
};

使用thread-loader进行打包加速

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "thread-loader": "3.0.4"
  }
}
npm install thread-loader@3.0.4 --save-dev
在vue.config.js中添加配置
const path = require('path');

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          include: path.resolve('src'),
          use: [
            {
              loader: 'thread-loader',
              options: {
                workers: 3
              }
            }
          ]
        }
      ]
    }
  }
};

使用compression-webpack-plugin插件进行压缩打包

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "compression-webpack-plugin": "4.0.1"
  }
}
npm install compression-webpack-plugin@4.0.1 --save-dev
在vue.config.js中添加配置
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
    publicPath: '/',
    productionSourceMap: false,
    lintOnSave: false,
    configureWebpack: {
        plugins: [
            new CompressionPlugin({
                algorithm: 'gzip',
                test: /\.js$|\.html$|\.css$/,
                filename: '[path].gz[query]',
                minRatio: 0.8,
                threshold: 10240,
                deleteOriginalAssets: false
            })
        ]
    }
}

启用Nginxgzipgzip_static功能

参考资料
在nginx.conf中添加配置

该配置支持位置:httpserverlocation

http {
    gzip_static         on;
    gzip_proxied        expired no-cache no-store private auth;
    gzip                on;
    gzip_min_length     1024;
    gzip_buffers        32 4k;
    gzip_comp_level     5;
    gzip_vary           on;
    gzip_types          text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    gzip_disable        "MSIE [1-6]\.";
}
常用Nginx配置参考
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    # access_log  /var/log/nginx/host.access.log  main;

    root /opt/pamirs;

    location / {
        try_files $uri $uri/ /index.html;
        index  index.html index.htm;
        expires 1d;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache nginx_cache;
        proxy_cache_valid 200 302 1d;
        proxy_cache_valid 404 10m;
        proxy_cache_valid any 1h;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($request_filename ~* ^.*?.(html|htm|js)$) {
            add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
        }
    }

    location /pamirs {
        proxy_pass          http://127.0.0.1:8091;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /pamirs/openapi {
        proxy_pass          http://127.0.0.1:8092;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

PS:127.0.0.1:8091表示服务端访问地址;127.0.0.1:8092表示服务端EIP访问地址;

使用本地OSSNginx配置参考

静态资源文件包下载

server {
    location /file/upload {
        proxy_pass              http://127.0.0.1:8081;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /static {
        alias /opt/pamirs/static;

        add_header Content-Disposition attachment;
        add_header Content-Type application/octet-stream;
    }

    location /css/static {
        alias /opt/pamirs/static;

        add_header Content-Disposition attachment;
        add_header Content-Type application/octet-stream;
    }
}

PS:127.0.0.1:8091表示服务端访问地址;

使用OSS加速静态资源访问(可选)

本文以阿里云为例进行讲解,其他OSS可自行翻阅资料进行配置。

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "webpack-aliyun-oss": "0.5.2"
  }
}
npm install webpack-aliyun-oss@0.5.2 --save-dev
在vue.config.js中添加配置,并填入OSS配置
const WebpackAliyunOss = require('webpack-aliyun-oss');

const OSS_URL = 'https//';
const OSS_DIST = "/pamirs/oinone/kunlun"
const OSS_REGION = '';
const OSS_ACCESS_KEY_ID = '';
const OSS_ACCESS_KEY_SECRET = '';
const OSS_BUCKET = '';

const UNIQUE_KEY = Math.random();

module.exports = {
  publicPath: `${OSS_URL}/${OSS_DIST}/${UNIQUE_KEY}/`,
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    plugins: [
      new WebpackAliyunOss({
        from: ['./dist/**/**', '!./dist/**/*.html'],
        dist: `${OSS_DIST}/${UNIQUE_KEY}`,
        region: OSS_REGION,
        accessKeyId: OSS_ACCESS_KEY_ID,
        accessKeySecret: OSS_ACCESS_KEY_SECRET,
        bucket: OSS_BUCKET,
        timeout: 1200000,
        deleteOrigin: true,
        deleteEmptyDir: true,
        overwrite: true
      })
    ]
  }
};

Q/A

为什么需要使用压缩打包方式生成.gz文件?

通过Nginx启用gzip压缩虽然也能达到压缩传输的目的,但gzip模块实际上在每次传输时(非disk cache)都会耗费cpu性能对文件内容进行压缩后再进行传输。

文中对Nginx的优化不仅启用了gzip模块,还启用了gzip_static模块。而gzip_static模块在传输时默认会优先使用.gz已经压缩好的文件直接进行传输。这种方式在支持gzip_static浏览器(如Chrome)上可以大大提高访问性能,并且可以减少服务器压力,综合提高服务器响应能力。

演示文件传输

image.png

使用gzip的响应头

image.png

image.png

使用gzip_static的响应头

image.png

image.png

使用OSS而不是通过Nginx进行静态文件的分发有什么好处?

一般来说,云厂商提供的OSS服务不仅是一个高性能的对象存储服务,搭配云厂商提供的CDN加速服务,可以不限地域的使用云资源,以此来提高页面响应速度。

阿里云的OSS是否也支持gzipgzip_static

支持

附录

下面是在本文中提到的完整配置

package.json
{
  "scripts": {
    "build": "vue-cli-service build --mode production"
  },
  "devDependencies": {
    "compression-webpack-plugin": "4.0.1",
    "dotenv-webpack": "1.7.0",
    "thread-loader": "3.0.4",
    "webpack-aliyun-oss": "0.5.2"
  }
}
vue.config.js

在该配置中,当process.env包含DEPLOY=online时自动使用OSS

const Dotenv = require('dotenv-webpack');
const path = require('path');
const WebpackAliyunOss = require('webpack-aliyun-oss');
const CompressionPlugin = require("compression-webpack-plugin");

let BASE_URL = '/';
const { DEPLOY, OSS_REGION, OSS_DIST, OSS_URL, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET } = process.env;
const UNIQUE_KEY = Math.random();
switch (DEPLOY) {
  case 'online':
    BASE_URL = `${OSS_URL}${UNIQUE_KEY}/`;
    break;
  default:
    BASE_URL = '/';
}

module.exports = {
  publicPath: BASE_URL,
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          include: path.resolve('src'),
          use: [
            {
              loader: 'thread-loader',
              options: {
                workers: 3
              }
            }
          ]
        }
      ]
    },
    plugins: [
      new Dotenv(),
      new CompressionPlugin({
        algorithm: 'gzip',
        test: /\.js$|\.html$|\.css$/,
        filename: '[path].gz[query]',
        minRatio: 0.8,
        threshold: 10240,
        deleteOriginalAssets: false
      }),
      DEPLOY === 'online'
        ? new WebpackAliyunOss({
          from: ['./dist/**/**', '!./dist/**/*.html'],
          dist: `${OSS_DIST}/${UNIQUE_KEY}`,
          region: OSS_REGION,
          accessKeyId: OSS_ACCESS_KEY_ID,
          accessKeySecret: OSS_ACCESS_KEY_SECRET,
          bucket: OSS_BUCKET,
          timeout: 1200000,
          deleteOrigin: true,
          deleteEmptyDir: true,
          overwrite: true
        })
        : () => { }
    ]
  },
  chainWebpack: (config) => {
    config.plugin('html').tap((args) => {
      args[0].chunksSortMode = (a, b) => {
        if (a.entry !== b.entry) {
          // make sure entry is loaded last so user CSS can override
          // vendor CSS
          return b.entry ? -1 : 1;
        } else {
          return 0;
        }
      };
      return args;
    });
    config.optimization.splitChunks({
      ...config.optimization.get('splitChunks'),
      cacheGroups: {
        libs: {
          name: 'chunk-a',
          test: /[\\/]node_modules[\\/]/,
          priority: 1,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        kunlun: {
          name: 'chunk-b',
          test: /[\\/]node_modules[\\/]@kunlun[\\/]/,
          priority: 2,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        common: {
          name: 'chunk-c',
          minChunks: 2,
          priority: 3,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    });
  },
  devServer: {
    port: 8080,
    disableHostCheck: true,
    progress: false,
    proxy: {
      '/pamirs': {
        changeOrigin: true,
        target: 'http://127.0.0.1:8080'
      }
    }
  }
};

Oinone社区 作者:张博昊原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/6796.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
张博昊的头像张博昊数式管理员
上一篇 2024年4月19日 pm8:46
下一篇 2024年4月22日 pm2:34

相关推荐

  • 页面出现中文乱码,该怎么解决?

    可能性1: 后端读取视图的xml解析时,由于系统缺少中文字体,导致解析后出现乱码,这种问题常见于采用docker镜像部署的情况,很多基础镜像不带中文字体。 解决方案:在物理系统或者docker镜像内安装中文字体 可能性2: win环境下未指定文件的编码类型 解决方案: 启动命令中加上-Dfile.encoding=UTF-8参数 # 示例命令 java -jar -Dfile.encoding=UTF-8 pamirs-demo-boot-1.0.0-SNAPSHOT.jar -Plifecycle=INSTALL

    2023年11月1日
    98000
  • oinone的GraphQL使用指南

    如果之前没了解过GraphQL,可以先查看GraphQL的文档 为什么oinone要选用GraphQL? 我们先看一下oinone独特的元数据设计 介绍信息来源于Oinone 7天从入门到精通,如提示无权限,则需要申请 再看一下GraphQl的介绍 我们可以看出,GraphQL提供的特性可以满足我们对元数据的描述需求,因此我们选用GraphQL。 相关工具推荐 可视化gql请求工具: 官方下载地址 oinone工具包-win版 oinone工具包-mac版 模拟登录请求 点击“Refresh Schema”按钮手动同步后端的gql文档数据 点击“show Documentation”按钮查看gql文档,可以在搜索框内输入关键字查询相关文档

    2023年11月1日
    99000
  • oinone的rsql与传统sql语法对照表

    rsql sql 描述 field01 == "name" field01 = "name" 等于 field01 != "name" field01 != "name" 不等于 field01 =gt= 1 field01 > 1 大于 field01 =ge= 1 field01 >= 1 大于等于 field01 =lt= 1 field01 < 1 小于 field01 =le= 1 field01 <= 1 小于等于 field01 =isnull=true field01 is null 字段为null field01 =notnull= 1 field01 is not null 字段不为null field01 =in= ("foo") field01 in ("foo") 多条件 field01 =out= ("foo") field01 not in ("foo") 不在多条件中 field01 =cole= field02 field01 = field02 字段作为查询参数 field01 =colnt= field02 field01 != field02 字段作为查询参数 field01 =like="foo" field01 like "%foo%" 全模糊匹配,rsql语法中无需拼接通配符”%“ field01 =starts="foo" field01 like "foo%" 前缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =ends="foo" field01 like “%foo" 后缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =notlike="foo" field01 not like "%foo%" 全模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notstarts="foo" field01 not like "foo%" 前缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notends="foo" field01 not like “%foo" 后缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =has=(ENUM_NAME1, ENUM_NAME2) 有多值枚举中的几个值 field01 =hasnt=(ENUM_NAME1,ENUM_NAME2) 没有多值枚举中的几个值 field01 =bit=ENUM_NAME1 有二进制枚举中的单个值 field01 =notbit=ENUM_NAME1 没有二进制枚举中的单个值 前端代码中使用工具类拼接rsql 该工具类在oinone的前端基础框架中提供 import { Condition } from '@kunlun/dependencies'; const rsqlCondition = new Condition('field01').equal('foo') .and(new Condition('field02').in(['bar'])) .and(new Condition('field03').notIn(['foo'])) .or(new Condition('field04').greaterThanOrEuqalTo(12)) .or(new Condition('field05').like('foo')) .or(new Condition('field06').notStarts('bar')) .or(new Condition('field07').isNull()) .or(new Condition('field08').notNull()) .and(new Condition('field09').bitEqual('BIT_ENUM_1')) .and(new Condition('field10').bitNotEqual('BIT_ENUM_2')) .and(new Condition('field11').has('ENUM_NAME_1')) .and(new Condition('field12').hasNot(['ENUM_NAME_2', 'ENUM_NAME_3'])); const rsqlStr = rsqlCondition.toString();…

    2023年11月1日
    3.9K00
  • 如何通过浏览器开发者工具提高调试效率

    1.通过vue devtool查看vue组件和oinone组件的信息 平台字段、动作、视图组件都是以Widget结尾的,可以通过关键字快速找到组件 安装vue devtool插件 chrome安装最新版的vue devtool插件 谷歌应用商店插件地址,隐藏窗口需要在扩展程序的详情页额外设置才能使用该插件 安装好插件后,可以通过插件选中html页面中的元素查看相关信息 相关特性了解 组件自动创建的vue组件以组件的class类名命名,据此特性可以得出自定义组件的时候,可继承该命名的父类 一般oinone的组件以Widget后缀命名,也推荐大家也以此为后缀,方便在vue调试面板快速看出哪些是oinone组件 选中oinone组件后可以在右侧面板看到相关的组件信息,部分属性介绍 template属性为dsl的配置 activeRecords、formData、rootData、parentData、dataSource等属性为常用数据属性 action为动作的元数据 field为字段的元数据 2.直接在浏览器开发者工具的源码处调试源码 打开浏览器开发者工具,进入标签页源代码/来源(英文版为Source),win系统用快捷键ctrl+O,mac系统用快捷键cmd+O, 可以打开文件搜索面板,通过关键字搜索文件名找到代码文件,可直接在里面debug调试或者查看执行逻辑

    2024年9月9日
    1.1K00
  • 左树右表默认选择第一行

    import { BaseElementWidget, Widget, SPI, ViewType, TableSearchTreeWidget } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'tree', model: '改成当前视图的模型' }) ) export class CustomTableSearchTreeWidget extends TableSearchTreeWidget { protected hasExe = false; @Widget.Watch('rootNode.children.length') protected watchRootNode(len) { if (len && !this.hasExe) { this.hasExe = true; const firstChild = this.rootNode?.children?.[0]; if (firstChild) { this.onNodeSelected(firstChild); this.selectedKeys = [firstChild.key]; } } } }

    2024年11月26日
    1.1K00

Leave a Reply

登录后才能评论