vue中使用cdn

8,450 阅读4分钟
让你明明白白学知识,有代码,有讲解,抄的走,学的会!

什么是cdn?

cdn是一套内容分发服务,其本质是将一些公共资源缓存到离用户最近的服务器上,减少直接从数据源服务器获取资源的响应时间。 我们使用公共的cnd资源,可以极大的减轻对服务器的压力,实质是减少了对我们自己服务器的http请求

使用cdn前后对比

在没有使用cnd的情况下,假设某个项目使用到如下的技术栈

  • vue
  • vuex
  • vue-router
  • echarts
  • axios
  • .... 等

我们一次性引入,不做按需加载的情况下,资源大小

总共资源 16.8M, 还是比较大的,目前这个没有加入 babel的编译成ES5代码的情况下,已经这么大了

上面这个是开发环境, 没有Tree-Shaking 的情况下,总共有16.8M这么大的资源需要加载;

如果不使用CDN, 在生产环境下,使用 Tree-Shaking 剔除冗余代码,代码体量会微微比 16.8M小一些,不过也不会有太明显的变化,因为,那些依赖包 vue、vuex等依旧是被打包进 chunk-vendor中了

main.js

import Vue from 'vue'
import App from './App.vue'

import ELEMENT from 'element-ui'
import "element-ui/lib/theme-chalk/index.css";
import axios from 'axios'
import * as echarts from 'echarts';
import VueRouter from 'vue-router'
import Vuex from 'vuex'
 import router from './router'

Vue.use(ELEMENT)

Vue.use(VueRouter)
Vue.use(Vuex)


Vue.prototype.$http = axios
Vue.prototype.$echarts = echarts
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

生产环境下,没有使用CDN的火焰图, vue-cli 3脚手架自动开启了tree-shaking

开启CDN加速

我们要将 vue、 vue-router、 vuex、element-ui 从 vendor.js 中分离出来,使用CDN资源引入。国内的CDN服务推荐使用 BootCDN

我们需要修改以下几个地方 vue-cli 3脚手架,需要在项目根目录新建一个 vue.config.js 文件,导出我们的webpack配置

vue-cli 2脚手架,需要添加对应的配置,其本质都是一样的,只不过vue-cli3 将一些配置隐藏了

vue.config.js

module.exports = {
  configureWebpack: {
    externals: {
      // 排除一些包,不会打包进vendor中
      // 左侧为我们在业务中引入的包名, 右侧 为对应库提供给外部引用的名字
      "vue": "Vue",
      "vue-router": "VueRouter",
      "vuex": "Vuex",
      "axios": "axios",
      "element-ui": "ELEMENT",
      "echarts": "echarts"
    }
  }
}

左侧的的名字 是我们 import elementUI from 'element-ui' 包的名字; 右侧的 “ELEMENT” 是每个包根据 UMD 规范,导出的名称。

UMD怎么查看对外暴露给浏览器使用的包名

以element-ui 为例 这是 cdn的地址

cdn.bootcdn.net/ajax/libs/e…

!(function(e, t) {
    // t就是实际的代码
    // 判断是不是node 使用 CommonJS规范
    "object" == typeof exports && "object" == typeof module ? 
    // 是CommonJS-直接引入包
    module.exports = t(require("vue")) : 
    // 不是 CommonJS,再判断AMD
    "function" == typeof define && define.amd ? 
    // 是AMD的加载方式,t就是具体包的自执行函数
    define("ELEMENT", ["vue"], t) : "object" == typeof exports ? exports.ELEMENT = t(require("vue")) : 
    // 不是上面2重,原生,全局加载 挂载到window对象上
    e.ELEMENT = t(e.Vue)
}
)("undefined" != typeof self ? self : this, function somepackage(e) {// 。。。 do something
})

// ELEMENT 就是暴力给浏览器用的名字

我们只需要将前面的一部分代码拎出来看,具体某个somepackage包的实现 不用管

现在我们是cdn,那么就需要找到浏览器能够识别的方式去加载我们的依赖包

有的库是明确告诉你 使用CDN 对外爆露的包名是什么

其他的包名,都是类似的方式找

需要修改的文件

因为引入的包名和 该包对外暴露的名字一致,所以,不需要修改什么,下面就将包的css注释掉即可 main.js

import Vue from 'vue'
import App from './App.vue'

import ELEMENT from 'element-ui'
// 注释,这个已经通过CDN 加载了
// import "element-ui/lib/theme-chalk/index.css";
import axios from 'axios'
import * as echarts from 'echarts';
import VueRouter from 'vue-router'
import Vuex from 'vuex'
 import router from './router'

Vue.use(ELEMENT)

Vue.use(VueRouter)
Vue.use(Vuex)

Vue.prototype.$http = axios
Vue.prototype.$echarts = echarts
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

修改 public/index.html

<!DOCTYPE html>
<html lang="en">
 <head>
 <!--别忘记引入element-ui的css资源-->
  <link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.2/theme-chalk/index.css" rel="stylesheet">
  </head>
  <body>
    <div id="app"></div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vuex/3.5.1/vuex.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.2.0/vue-router.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.2/index.js"></script>
  </body>
</html>

这里直接使用bootcdn 提供的线上资源

下图是 开发环境加载的截图,明显 总体资源的包体积缩小,这还没有开启GZip压缩和使用tree-shaking的状态

图-开发环境-没有tree-shaking的资源加载截图

整体资源 3.1M


生产环境,使用cdn, 有使用tree-shaking, 无gzip压缩的截图

图-生产环境,使用cdn, 有使用tree-shaking, 无gzip压缩的截图

整体资源 1.8M ,chunk-vendor 25.7k

打包,生成焰火图

vue-cli3 脚手架内置了 webpack-bundle-analyzer

vue-cli4+ 及以上版本 见下面的单独设置

在打包之前,我先将package.json中的 dependencies包从 node_modules中删除

npm uninstall axios echarts element-ui vue vue-router vuex --save

在 package.json 中添加一行 命令

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    // 新增这个
    "analyze": "vue-cli-service build --report"
},

运行,生成焰火图, 会打包到dist目录下

npm run analyze

要查看焰火图,必须以一个服务的形式运行,也就是 http://localhost:8080/ 这种,不能是 file:// xxx/report.html 这种file协议

推荐使用 live-server 这个包

// 全局安装
npm i live-server -g

// 切换到dist目录
cd dist

live-server report.html

如果开启GZip压缩以后,文件体积会更小

只有整体资源只有11k左右

nginx 部署,开启GZip压缩

将代码打包 , 将dis下面文件都拷贝到 nginx指定目录

window下启动 nginx

// 打开 cmd ,直接nginx 没有输出,就是最好的输出,那就是启动成功了
nginx

这是没有开始 GZip压缩的样子

开启以后

源文件4.4k, 压缩以后 2.2k

nginx 设置

http {
  gzip on;
  gzip_static on;
  gzip_min_length 1024;
  gzip_buffers 4 16k;
  gzip_comp_level 2;
  gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
  gzip_vary off;
  gzip_disable "MSIE [1-6]\.";
}

如果你要觉得哪种类型的文件没有被压缩,那可能就是 gzip_types 没有配置对, 还有gzip_comp_level压缩等级也是需要关注的,压缩等级越高,说明服务器需要耗费的时间就越长; 太高的压缩等级没有什么意义,根据实际的需求,取一个差不多的值,即可

修改完nginx配置以后,一定要重启; 然后 window上启动nginx以后,发现停不了,请使用 ‘任务管理器’ 手动在 ‘详细信息’ 里面 结束任务, linux中不存在这种情况

在线网站查看是否已经开启了Gzip压缩 这个只能是线上的,不能是本地的,除非有 内网穿透工具,将本地的服务映射成外网,也可以使用

vue-cli4, vue-cli5 开启 analyzer

# 运行
npx vue-cli-service -help

发现没有 vue-cli3 脚手架中的 --report 选项了

image.png

analyzer 本质是一个webpack 插件, 手动配置下

安装依赖

npm i cross-env webpack-bundle-analyzer -D


# 顺便标注下我演示的几个核心版本
vue-cli-service: 4.4.6
"cross-env": "^7.0.3",
"webpack-bundle-analyzer": "^4.5.0"

package.json增加脚本

{
    "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "analyzer": "cross-env NODE_ENV=analyzer vue-cli-service build"
    }
}

这里增加 NODE_ENV=analyzer 可以根据自身需求调整 vue.config.js 中 chainWebpack 的场景判断

修改vue.config.js

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

/**
 * 静态CDN文件
 */
const assetsCDN = {
    externals: {
        // 左侧key为项目中引入的名称,右侧value为包对外提供的名称
        vue: 'Vue',
        'vue-router': 'VueRouter',
        vuex: 'Vuex',
        'element-ui': 'ELEMENT'
    },
    css: [
        '//cdn.jsdelivr.net/npm/element-ui@2.15.0/lib/theme-chalk/index.css'
    ],
    js: [
        '//cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js',
        '//cdn.jsdelivr.net/npm/vue-router@3.4.9/dist/vue-router.min.js',
        '//cdn.jsdelivr.net/npm/vuex@3.6.0/dist/vuex.min.js',
        '//cdn.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.min.js',
        '//cdn.jsdelivr.net/npm/element-ui@2.15.0/lib/index.js'
    ]
}

function injectCDN(config) {
    // 非生产环境,不启用CDN
    if(process.env.NODE_ENV !== 'production') return

    config.set('externals', assetsCDN.externals)

    config.plugin('html').tap(args => {
        args[0].cdn = assetsCDN
        return args
    })
}


module.exports = {
    chainWebpack(config) {
        injectCDN(config)
    }
}

修改public/index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
     <!-- 使用CDN的CSS文件 -->
     <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
        <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
    <% } %>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <!-- 使用CDN的JS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
        <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
  </body>
</html>

注意事项:

  • index.html 中增加的注入js的脚本代码,不能放在 body 标签后面, 需要放在body标签里面