Vue根据标签实现按需加载

实际开发中发现一个项目中会使用到多个组件库,对于这些组件库有一些使用的是按需加载,但是也有一些使用的是全局注册。
对于一些大的第三方组件库需要按需导入。

如:import ElButton from 'element-ui/lib/button;
但是这种按需导入在页面使用组件非常多的场景时,开发繁琐,体验不友好。
当然这些组件库也会推荐一些babel插件来提升开发体验和性能优化。
babel-plugin-componentbabel-plugin-importbabel-plugin-transform-imports等。
babel-plugin-transform-imports可以通过自己的配置来实现以下效果。

import { Row, Grid as MyGrid } from react-bootstrap'; 
import { merge } from 'lodash';
↓ ↓ ↓ ↓ ↓ ↓
import Row from 'reactbootstrap/lib/Row';
import MyGrid from 'react-bootstrap/lib/Grid';
import merge from 'lodash/merge';`

babel-plugin-transform-imports同样有一些缺陷:它无法由一个导入生成多个导入。

这也就意味着使用babel-plugin-transform-imports无法对antdelement-ui这样的UI组件库进行模块按需导入:这些UI组件库,除了js模块的导入外,往往还有一个样式模块。

详情请参考https://bitbucket.org/amctheatres/babel-transform-imports/src/master/

因此参考babel-plugin-transform-imports实现一个vue项目的组件按需导入。

1. 获取页面所有标签。

Vue中的每个页面其实都是一个.vue的文件,这种文件,Vue称之为组件页面,其使用vue-loader来进行解析。

当解析某个vue页面时通过node-html-parser来获取到当前页面的所有标签。使用sync-disk-cache来将获取到的标签保存在本地。

image.png

image.png

页面源码会被vue-loader解析成上图所示,其中包含四种类型的代码块。

一、因此根据loaderresourceQuery属性是否含有type=template来获取template块。

二、使用node-html-parsertemplate代码块进行解析(深层遍历,去重)

三、创建sync-disk-cache对象,暴露getsetclear方法

2. 使用babel在代码编译时将目标标签按照特定规则进行引入、并将组件进行注册。

一、babel插件暴露lib(配置组件导入地址)、style(配置样式导入地址)方法。

二、在visitor指定 Program节点,并根据路径获取sync-disk-cache保存的本地标签。

三、在export default语句中完成剩下的逻辑, 也就是表达式ExportDefaultDeclaration的访问器中:

因为组件最终导入的形式为:

import Comp1 from 'path/to/comp1';
import Comp2 from 'path/to/comp2';

vue文件中,script里面一定会导出一个对象

export default {
   data() {
       return {};
   },
 };

此时要将Comp1Comp2注册则需要写成以下这种形式。

export default {
   components: {
       Comp1: Comp1,
       Comp2: Comp2,
   },
   data() { 
       return {};
   },
};
   

理论来说需要这样,但是这样去改太复杂了,虽然有babel的帮助,至少要考虑:

  1. 当前组件的配置对象里面是否有components这个选项,没有还需要特殊的复杂处理
  2. 当前组件的配置对象里面有components这个选项,低于注册的那个组件,他是不是已经注册过了,如果是,还有可能语法报错。
  3. 他是不是复杂的组件配置对象的写法,比如
const config = {
   components: {},
   data: ...
   // ....
};
export default config;

还有可能是这样的:

const compontns = { ... };
export default {
   components: compontns,
   data: ...
   // ....
};

要处理的情况太多了,从另一个角度想,它最终导出的一定是一个组件的配置对象,因此可以把它解出来。
比如 export default ....
不管他这个....是一个字面量还是一个变量都转换成

const _thisCpnponentConfig = ...
export _thisCpnponentConfig;

在这种情况下如果要给他注册组件只需扩展这个_thisCpnponentConfig里面的components选项就好了。
因此上面的代码可以写成

const _thisCpnponentConfig = ...;
// 先初始化一下,避免他开始没有
thisCpnponentConfig.components = thisCpnponentConfig.components || {};
thisCpnponentConfig.components['Comp1'] = Comp1;
export _thisCpnponentConfig;

3. 如何使用

npm i vue-template-label-loader;
npm i babel-plugin-vue-auto-import;

3.1.1. 在webpack配置中配置该loader:

const { clear } = require('vue-template-label-loader/lib/store');
// 在每次构建时, 都清空上一次存储信息。
clear();
module.exports = {
   module: {
       rules: [{
           test: /\.vue$/,
           use: [{
               loader: 'vue-template-label-loader',
               options: {}
               exclude: {}
           },{
               loader: 'vue-loader',
               options: {
               //...
               },
           }]
       },
   ]}
}; 

3.1.2. 在vue.config.js中

const { clear } = require('vue-template-label-loader/lib/store');
// 在每次构建时, 都清空上一次存储信息。
clear();
module.exports = {
   chainWebpack: (config) => {
       config.module
           .rule('vue')
           .set('exclude', [/node_modules/])
           .use('vue-template-label-loader')
           .loader('vue-template-label-loader')
           .end()
       },
};

3.2. 配置babelrc.js

该工具需要配合 vue-template-label-loader 使用

function isCapitalStart(string) {
  if (!string) {
    return false
  }
  const first = string[0];
  const reg = new RegExp(/[A-Z]/);
  return reg.test(first);
}


function toLine(string) {
  return string.replace(/([A-Z][a-z]*)([A-Z][a-z]*)/g,"$1-$2").toLowerCase();
}

function kebabCase(str) {
  var hyphenateRE = /([^-])([A-Z])/g;
  return str
    .replace(hyphenateRE, '$1-$2')
    .replace(hyphenateRE, '$1-$2')
    .toLowerCase();
}

module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'],
  plugins: [
    [
      'babel-plugin-vue-auto-import',
      {
        // excludeTags: ['List', 'HelloWorld'],
        lib(tag) {
          // 如果某个标签需要自动导入,请返回导入路径, 不需要则返回null
          if (tag.startsWith('el-')) {
            return `element-ui/lib/${tag.replace('el-', '')}`;
          }
          if (tag.startsWith('a-')) {
            return `ant-design-vue/lib/${tag.replace('a-', '')}`;
          }
          if(isCapitalStart(tag) || tag.startsWith('i-')) {
            const tagName = tag.startsWith('i-') ? tag.replace('i-', '') : toLine(tag)
            return `view-design/src/components/${toLine(tagName)}/index.js`
          }
          return null
        },
        style(tag) {
          // 如果某个标签需要自动样式文件,请返回导入路径,无则返回null
          if (tag.startsWith('el-')) {
            const label = tag.replace('el-', '');
            return `element-ui/lib/theme-chalk/${label}.css`;
          }
          if (tag.startsWith('a-')) {
            const tagName = tag.replace('a-', '');
            return `ant-design-vue/lib/${tagName}/style`;
          }
          return null;
        },
      },
    ],
  ],
};

4. 注意事项

因为element-ui部分组件有使用到icon但是组件不会去自动导入icon,因此icon需要手动进行全局导入。

import Vue from 'vue'
import { Icon } from 'element-ui'
import 'element-ui/lib/theme-chalk/icon.css';
Vue.component(Icon.name, Icon)

ant-desgin-vuemodel组件使用时会报错,具体原因是,按需引入的常用写法中没有调用到Vue.use所执行的自定义指令。解决方案如下

// main.js
import { Modal }from 'ant-design-vue';
Modal.install(Vue)` 

view-desgin 根据官方文档,需要在main.js将样式全部导入

import 'view-design/dist/styles/iview.css';

具体使用可以参考https://github.com/dexterBo/vue-auto-tag

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

推荐阅读更多精彩内容