Skip to content
On this page

公共 CDN

公共 CDN 的优缺点

缺点

  1. 无法整合优化代码,导致初次访问网站时加载的文件总体积稍大。

  2. 公共 CDN 提供的是免费服务,流量消耗极大,可能出现地区性故障,当出现故障时,你会觉得还不如不使用公共 CDN。

优点

  1. 免费

  2. 高速

  3. 可能用户在访问其他网站时已经缓存了公共 CDN 的文件,会让访问我站时速度更快。

本文假设你采用了公共 CDN,并且会提供托底方案。

公共 CDN 与私有 CDN 的使用原则

公共 CDN

  1. 凡著名的库一律使用公共 CDN。

  2. 非著名的库如果大于 5KB 也使用公共 CDN。

  3. 只有小于 5KB 的小众库使用 NPM 本地安装、打包。

私有 CDN

/dist/下的文件一律使用私有 CDN,其中有 hash 的文件和没有 hash 的文件要区别对待:

  1. 凡文件名含有 hash 的文件,一般来说只有/static/目录下的文件,设置 1 年强制缓存

  2. 若文件名不含 hash,比如index.htmlfavicon.png等,使用协商缓存

公共 CDN 部署方案

1. 配置 vue.config.js 的 configureWebpack

js
module.exports = {
  configureWebpack: {
    externals: {
      vue: 'Vue',
      'vue-router': 'VueRouter',
      vuex: 'Vuex',
      //...
    },
  },
};

其中externals键值对规则:

  • 键名必须是库的 NPM 注册名,也是/node_modules/里的文件夹名。

  • 键值必须是库的主文件里声明的全局变量名,需要打开库的主文件查看,前几行内会有global.Vueglobal.Vuex或类似代码,或者最末行会有window.L或类似代码,这意味着 CDN 加载方式会给window添加VueVuexL等全局变量,这些变量名就是我们要的值。不要妄自猜测库的全局变量名,比如leaflet的全局变量名就不是leafletLeaflet。著名库的键值对举例如下:

键名键值
vueVue
vue-routerVueRouter
vuexVuex
axiosaxios
element-uiELEMENT
element-plusElementPlus
echartsecharts
jqueryjQuery
momentmoment
dayjsdayjs
video.jsvideojs
leafletL
quillQuill

2. 配置 vue.config.js 的 chainWebpack

js
module.exports = {
  chainWebpack(config) {
    config.plugin('html').tap((args) => {
      args[0].CDN = {
        official: {
          development: [
            '<link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.7/theme-chalk/index.css" type="text/css" rel="stylesheet" />',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vuex/3.6.2/vuex.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.5.3/vue-router.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.7/index.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.js" crossorigin="anonymous"></script>',
          ],
          production: [
            '<link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.7/theme-chalk/index.min.css" type="text/css" rel="stylesheet" />',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.min.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vuex/3.6.2/vuex.min.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.5.3/vue-router.min.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.7/index.min.js" crossorigin="anonymous"></script>',
            '<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js" crossorigin="anonymous"></script>',
          ],
        },
        spare: {
          production: [
            '<link href="/cdn/element-ui/2.15.7/theme-chalk/index.min.css" type="text/css" rel="stylesheet" />',
            '<script src="/cdn/vue/2.6.14/vue.min.js"></script>',
            '<script src="/cdn/vuex/3.6.2/vuex.min.js"></script>',
            '<script src="/cdn/vue-router/3.5.3/vue-router.min.js"></script>',
            '<script src="/cdn/element-ui/2.15.7/index.min.js"></script>',
            '<script src="/cdn/axios/0.24.0/axios.min.js"></script>',
          ],
        },
      };
      return args;
    });
  },
};

注意:

  1. 一个健全的方案,必须要对公共 CDN 采取托底措施,以防范公共 CDN 服务故障,所以,使用official表示正式库的代码,它使用公共 CDN,使用spare表示备用库的代码,它使用私有 CDN。

  2. official又分为developmentproduction对应开发环境和生产环境,其差别是分别对应未压缩的代码和压缩过的代码,开发环境使用未压缩的代码是为了快速定位库中的报错。spare没有development字段是因为备用库仅用于生产环境。

  3. 公共 CDN 尽量采用靠谱的、稳定的、受国情和法律政策影响小的服务商,一旦发现问题,请尽早替换服务商。

  4. 私有 CDN 的文件路径尽量按照规范格式。

3. index.html 中添加 CDN

注意:下方代码中的process会被本网站的解析引擎错误解析,所以只能添加了几个混淆字符,使用时应删掉。

html
<head>
  <!-- ... head 原有内容... -->

  <% if (pro【混淆字符,请删除我】cess.env.NODE_ENV === 'development') { %> <%=
  htmlWebpackPlugin.options.CDN.official.development.join('') %> <% } else { %> <%=
  htmlWebpackPlugin.options.CDN.official.production.join('') %> <% } %>
  <script>
    // 托底方案
    if (!window.Vue) {
      const spare = '<%= encodeURIComponent(htmlWebpackPlugin.options.CDN.spare.production.join("")) %>';
      document.write(decodeURIComponent(spare));
    }
  </script>
</head>

注意:

上方示例代码应作为<head></head>的结尾,也就是说不要在代码的下方插入其他 JS 或 CSS 资源,无论它来自于哪个域名,因为document.write会将备用代码写入<body></body>的最顶部,导致执行晚于<head></head>里的任何文件。所以将所有 JS 和 CSS 资源全写到developmentproduction里就 OK 了,维护更方便。

4. 处理项目 dist 下的文件

同上方《公共 CDN 与私有 CDN 的使用规则》一节,不再赘述。

5. 其他注意事项

  1. 是否必须把库名写入package.jsondependencies

可以不写入,但建议写入。

可以不写入的原因是:因为externals会让项目排除本地包,此时使用的其实是index.html里的 CDN 资源。

但是不写入也有缺点,比如:项目需要哪些库不能一目了然,而且无法便捷查看库源码。同时请考虑到,或许某些非 CDN 库会依赖 CDN 库,最终 CDN 库还是会被本地安装,倒不如把库名全写到dependencies

总之,建议把所有库都照常加入dependencies

  1. 业务代码是否必须写import语句?

可以全部省略,但建议不要省略。

可以省略的原因是,直接使用变量会被视为window下的变量,恰好 CDN 提供的都是window下的变量,所以不会出错。

但是省略也有缺点,比如将来一旦改造项目,某库由公共 CDN 引入改为本地引入,但从前你没有写import,所以此时需要补足语句,会徒增工作量。

总之,为了将来改造方便,为了兼容不采用公共 CDN 的方案,尽量还是写import语句。

  1. 业务代码是否应该照旧引入 CSS 资源?

不应照旧引入 CSS 资源。

因为<link />引入 CDN 的 CSS 和import(或@import)引入 CSS 会造成重复引用。

杨亮的前端解决方案