webpack使用HtmlWebpackPlugin进行cdn配置

CDN服务商的选择

在前面的文章中我们介绍了cdn的实现原理,现在我们来实现如何在正式开发中使用cdn功能。要使用cdn功能,就需要cdn服务商,我们可以自己搭建,也可以使用一些比较知名的服务商,庆幸的是市面上有不少的免费cdn服务商,如:

其中BootCDN 是 Bootstrap 中文网支持并维护的前端开源项目免费 CDN 服务,项目资源同步于 cdnjs 仓库。界面相对比较好看,且支持搜索功能,可以在线测试cdn是否正常,所以下面以Bootcdn为例说明。

webpack的作用

在前段项目开发中,我们经常使用webpack进行项目搭建, 主要作用有两个,分别是

  1. 启动服务器环境,用于调试代码
  2. 构建项目,生成静态资源


    webpack

在webpack中使用cdn是在打包生成静态资源的时候做处理,主要原理是使用html-webpack-plugin动态插入cdn链接。

关于webpack的使用这里不做过多的介绍,将以vue--cli 2.x生成的默认项目为例做介绍

html-webpack-plugin的使用

html-webpack-plugin是webpack的一个插件,可以动态的创建和编辑html内容,在html中使用esj语法可以读取到配置中的参数,简化了html文件的构建。

This is a webpack plugin that simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply your own template using lodash templates or use your own loader.

我们这次主要是使用它来动态插入cdn链接,如link标签和script标签。


创建Vue项目

在线项目地址

vue-cli 2.x

$ vue init webpack webpack-cdn-demo

创建名为webpack-cdn-demo,类型为webpack的vue项目,如果安装的vue-cli是3.x版本,命令不太一样,详细可看vue-cli 3

安装依赖

$ cd  webpack-cdn-demo
$ npm  install

启动项目

$ npm run dev

下面简单介绍一下目录结构

+-- build
|  +-- utils.js js 工具类
|  +-- webpack.base.conf.js  webpack基础配置
|  +-- webpack.dev.conf.js  webpack开发配置
|  +-- webpack.prod.conf.js  webpack构建配置
+-- config
+-- src
|  +-- App.vue  vue文件组件
|   +-- main.js  入口文件
+-- index.html  页面
+-- package.json  项目文件

其中build文件夹中的webpack.prod.conf.js是我们主要注意的文件,我们在该文件中动态设置不需要被打包的模块并构建出合适的链接。

确定需要使用CDN的模块

在webpack项目中,所引入的第三方资源会被统一打包进vender文件中,我们通过webpack的externals属性可以设置打包时排除该模块,详情说明见外部扩展(externals)

在前面的步骤中,我们创建的项目包括vue、vue-router,在正式开发在还会有ui库,如element-ui,为了方便演示,我们再安装element-ui和axios两个模块,并实现在构建是把这是个模块以cdn的形式引入。

所需模块
vue
vue-router
element-ui
axios
$ npm install element-ui axios  -S

注意安装时记得-S,它的作用是安装完后在package.json项目文件中插入记录,后续操作需要读取已安装模块

确定CDN资源URI

对于cdn,我们可以自己搭建,也可以使用专业的cdn服务商,这里使用免费的cdn bootcdn。选用免费cdn有很多好处,但毕竟有隐患,那就是服务有可能会奔溃。

bootcdn https://www.bootcdn.cn

bootcdn

依次搜索出前面模块,结果如下

模块 版本 js css
vue 2.5.2 https://cdn.bootcss.com/vue/2.5.2/vue.min.js -
vue-router 3.0.1 https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js -
element-ui 2.6.3 https://cdn.bootcss.com/element-ui/2.6.1/index.js https://cdn.bootcss.com/element-ui/2.6.1/theme-chalk/index.css
axios 0.18.0 https://cdn.bootcss.com/axios/0.18.0/axios.min.js -

按照规律,得出cdn资源路径规则为

https://cdn.bootcss.com + 模块名 + 版本号 + 具体路径

其他cdn服务商同理


打包前的处理

build/utils.js添加读取事件

使用cdn其实也就是在webpack热启动和打包项目的时候动态插入script和style链接,为了方便维护,我们通过在build/utils.js文件上添加几个方法,将来在webpack.dev.conf.jswebpack.prod.conf.js上可以使用。

如果没有build/utils.js,可以在其他文件上添加,只要在后续步骤中能操作到就行

  1. 添加cdn根地址
// build/utils.js 国内免费cdn镜像源
exports.cdnBaseHttp = 'https://cdn.bootcss.com';
  1. 添加cdn模块 按照需要删改
//  build/utils.js external配置
exports.externalConfig = [
  { name: 'vue', scope: 'Vue', js: 'vue.min.js' },
  { name: 'vue-router', scope: 'VueRouter', js: 'vue-router.min.js' },
  { name: 'axios', scope: 'axios', js: 'axios.min.js' },
  { name: 'element-ui', scope: 'ELEMENT', js: 'index.js', css: 'theme-chalk/index.css' },
];

name 模块名称,与package.json同名
scope 模块作用域命名
js js地址
css css地址

这里特别注意scope,它是webpack配置的external参数下的信息,比如vue的作用域命名是Vue,vue-router的作用域命名是VueRouter,element-ui的作用域命名是ELEMENT,同理,jq的作用域命名是JQuery,具体做法是先引入该资源,然后在控制台依次输入近似的值,一个个匹配(目前没找到更好的做法)。

  1. 添加获取版本号方法
// build/utils.js 获取模块版本号
exports.getModulesVersion = () => {
  let mvs = {};
  let regexp = /^npm_package_.{0,3}dependencies_/gi;
  for (let m in process.env) { // 从node内置参数中读取,也可直接import 项目文件进来
    if (regexp.test(m)) { // 匹配模块
       // 获取到模块版本号
      mvs[m.replace(regexp, '').replace(/_/g, '-')] = process.env[m].replace(/(~|\^)/g, '');
    }
  }
  return mvs;
};
  1. 导出不需要被打包的cdn模块配置重点
// build/utils.js
exports.getExternalModules = config => {
  let externals = {}; // 结果
  let dependencieModules = this.getModulesVersion(); // 获取全部的模块和版本号
  config = config || this.externalConfig; // 默认使用utils下的配置
  config.forEach(item => { // 遍历配置
    if (item.name in dependencieModules) {
      let version = dependencieModules[item.name];
      // 拼接css 和 js 完整链接
      item.css = item.css && [this.cdnBaseHttp, item.name, version, item.css].join('/');
      item.js = item.js && [this.cdnBaseHttp, item.name, version, item.js].join('/');
      externals[item.name] = item.scope; // 为打包时准备
    } else {
      throw new Error('相关依赖未安装,请先执行npm install ' + item.name);
    }
  });
  return externals;
};
webpack.dev.conf.js添加cdn配置

在webpack热启动本地调试的时候,我们可以使用cdn。

  1. 获取cdn配置
// build/webpack.dev.conf.js 大概在15行
const externalConfig = JSON.parse(JSON.stringify(utils.externalConfig));  // 读取配置
utils.getExternalModules(externalConfig); // 获取到合适的路径(引用类型,自动改变)

// const devWebpackConfig = merge ....... 

build/webpack.dev.conf.js中,默认已经引入了utils.js,所以可以直接调用相关方法,如果是自定义的文件,记得引入。

  1. HtmlWebpackPlugin插件中导出cdn
    紧接着我们在该文件下找到devWebpackConfig下的plugins下的HtmlWebpackPlugin插件,它的作用是动态构建html页面,原始配置如下:
new HtmlWebpackPlugin({
  filename: 'index.html',
  template: 'index.html',
  inject: true
}),
// 代表处理根目录下的index.html文件

我们可以往里面添加点自定义属性,方便在index.html中调用。,修改如下:

new HtmlWebpackPlugin({
 filename: 'index.html',
 template: 'index.html',
 inject: true,
 cdnConfig: externalConfig, // cdn配置
 onlyCss: true, //dev下只加载css
}),

其中cdnConfigonlyCss自定义属性,在html上通过htmlWebpackPlugin.options可以读取到。

更多html-webpack-plugin配置情况官网,这里暂时不需要更多。

webpack.prod.conf.js添加cdn配置和忽略模块

在打包的时候,我们使用cdn,配置和前面dev的差不多,只不过需要做多一步。

  1. 获取cdn配置
// build/webpack.prod.conf.js 大概在15行
const externalConfig = JSON.parse(JSON.stringify(utils.externalConfig)); // 读取配置
const externalModules = utils.getExternalModules(externalConfig); // 获取到合适路径和忽略模块

// const webpackConfig = merge(baseWebpackConfig.... 

注意此处的externalModules,后面用到,也就是比dev多的步骤。

  1. webpck配置加多个属性externals
    externals代表构建时不需要被处理的模块,也就是前面说的scope需要注意的地方。
// build/webpack.prod.conf.js
const webpackConfig = merge(baseWebpackConfig, {
  externals: externalModules, // 构建时忽略的资源

  // 其他属性
}
  1. HtmlWebpackPlugin插件中导出cdn
    和dev一样,我们修改webpackConfig下的plugins下的HtmlWebpackPlugin插件配置 (这里的默认配置比dev的多,主要是css压缩和js压缩相关)
new HtmlWebpackPlugin({
   // 其他默认配置
  cdnConfig: externalConfig, // cdn配置
  onlyCss: false, //加载css
}),

加入和dev一样的两个配置,不过需要把onlyCss改为true,因为我们希望打包时不单单使用css。

index.html插入相关链接

webpack配置已经完成,在html-webpack-plugin中已经添加了相关参数,我们再在页面上可以直接使用,使用语法是ejs,和asp.net,jsp,php类似。

<!DOCTYPE html>
<html>
  <head>
   <!-- 其他标签 -->
    <% htmlWebpackPlugin.options.cdnConfig.forEach(function(item){ if(item.css){ %>
    <link href="<%= item.css %>" rel="stylesheet" />
    <% }}) %>
  </head>
  <body>
   <!-- 其他标签 -->
    <% htmlWebpackPlugin.options.cdnConfig.forEach(function(item){ if(item.js && !htmlWebpackPlugin.options.onlyCss){ %>
    <script type="text/javascript" src="<%= item.js %>"></script>
    <% }}) %>
    <!-- built files will be auto injected -->
  </body>
</html>

通过<% %>htmlWebpackPlugin.options 用js遍历插入link标签和script标签。

ps: 修改了webpack配置,需要重启项目才会生效


愉快的开发

此时启动项目,查看控制台或者查看源代码,可以清楚的相关资源来源是cdn
浏览器控制台

打包项目

$ npm run build
打包结果

可以看到打包体积大大减小了


network

页面上也正确引入了cdn资源。


image.png

最后奉上git地址:https://gitee.com/zhkumsg/webpack-cdn-demo


比悲伤更悲伤的分割线


原来两年前已经有人做了一个类型的webpack-cdn-plugin


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 文/李秀峰 年关欲至满愁容, 乡土难离寄旧情。 邀月同酌凌塔①酒, 且将此事付春风。 ——2018.2....
    盘锦西山阅读 213评论 2 17
  • 综述 我在全面剖析了自身的特质以后,针对于工程技术领域的经验丰富优势、科学研究领域的理论薄弱劣势。我制定了全面夯实...
    安德鲁罗文中尉阅读 272评论 0 0
  • 从来就认为自己是一个可以背起行囊,说走就走的人,是的,谁也别想束缚我。对于自己是射手座更是从骨子里认为灵魂都是与之...
    蓝印花布的袖子阅读 186评论 0 2