# 组件按需引入

# 一次性全部引入

组件库一开始只提供了一次性全部引入的方式。

# 导出组件库

  • 组件目录
├─packages
		├─button
		│		└─button.vue
		│		└─button.scss
    ├─input
    │		└─input.vue
		│		└─input.scss
		├─...
		│
		│ index.js  # 所有组件的入口
  • packages/index.js
import XButton from './button/button.vue'

const components = [
  XButton,
  ...
]

// 全局注册组件
const install = Vue => {
  components.forEach(c => {
    Vue.component(c.name, c)
  })
}

/**
 * 有可能组件会通过script标签引入,如<script src='https://xxx/xqh-vue-ui'></script>
 */
if (typeof Window.Vue !== 'undefined') {
  install(Vue) // 全局直接通过script 引用的方式会默认调用install方法
}

export default {
  install
}

# 打包组件库

  • package.json 中增加打包组件库指令
"script":{
  // 利用vue自带的工具 构建 目标(库)  库的名字xqh-vue-ui 当前的入口文件
  "lib": "vue-cli-service build --target lib --name xqh-vue-ui  packages/index.js"
}

打包后的目录

├─lib
  ├── demo.html
  ├── xqh-vue-ui.common.js
  ├── xqh-vue-ui.css
  ├── xqh-vue-ui.umd.js
  └── xqh-vue-ui.umd.min.js

# 上传 npm

  • 配置 package.json 文件
{
  "name": "xqh-vue-ui",
  "version": "1.0.0",
  "private": false,
  "main": "lib/xqh-vue-ui.umd.min.js",
  ...
}

# 导入组件库

  • 安装
npm install xqh-vue-ui
  • 使用
import Vue from 'vue'
import xqhVueUI from 'xqh-vue-ui'
import 'xqh-vue-ui/lib/xqh-vue-ui.css'
Vue.use(xqhVueUI)

缺点:有些项目只需要用到某几个组件,引入整个组件库,导致项目打包后体积庞大。

# 按需引入

为了降低首屏代码大小,对于一些大的第三方库或者团队的基础工具库,需要按需导入模块。因此本组件也增加了按需引入的方式。

核心思想:webpack 多入口打包。

# 配置生产环境和开发环境

  • process.env.NODE_ENV
    • process.envNode.js 中的一个环境对象。其中保存着系统的环境的变量信息。而 NODE_ENV 就是其中的一个环境变量。这个变量主要用于标识当前的环境(生产环境,开发环境)。默认是没有这个环境变量的,需要自己手动配置。
    • 默认情况下,一个 Vue CLI 项目有三个模式:
      • development 模式:用于 vue-cli-service serve
      • production 模式:用于 vue-cli-service build 和 vue-cli-service test:e2e
      • test 模式:用于 vue-cli-service test:unit
    • Vue 中, NODE_ENV 可以通过 .env 文件或者 .env.[mode] 文件配置。配置过后,运行 Vue CLI 指令( npm run dev(serve)npm run build )时,就会将该模式下的NODE_ENV载入其中了。

.env.dev

NODE_ENV = 'development'

.env.prod

NODE_ENV = 'production'
  • 修改组件库脚手架工具,增加额外打包配置。

vue.config.js

const devConfig = require('./config/config.dev')
const buildConfig = require('./config/config.build')

module.exports = process.env.NODE_ENV === 'development' ? devConfig : buildConfig

config/config.dev.js

let devConfig = {
	...
}
module.exports = devConfig;

config/config.build.js

let buildConfig = {
	...
}
module.exports = buildConfig;

package.json

"script":{
    "lib:dev": "vue-cli-service build --mode dev --target lib --name xqh-vue-ui --dest lib packages/index.js",
    "lib:prod": "vue-cli-service build --mode prod"
}

# buildConfig

核心:config/config.build.js

const { resolve, getComponentEntries } = require('./utils')

let buildConfig = {
  //  输出文件目录
  outputDir: resolve('lib'),
  productionSourceMap: false,
  //  webpack配置
  configureWebpack: {
    //  入口文件 多入口对象
    entry: getComponentEntries('packages'),
    //  输出配置 按需引入的关键!
    output: {
      //  文件名称
      filename: '[name]/index.js',
      //  构建依赖类型
      libraryTarget: 'umd',
      //  依赖输出
      libraryExport: 'default',
      //  依赖名称
      library: 'xqh-vue-ui'
    },
    // 防止将某些 import 的包(Vue)打包到bundle中,而是在运行时再去从外部获取这些外部依赖模块。
    externals: {
      vue: {
        root: 'Vue',
        commonjs: 'vue',
        commonjs2: 'vue',
        amd: 'vue'
      }
    }
  },
  //  样式输出
  css: {
    sourceMap: true,
    extract: {
      filename: '[name]/style.css'
    }
  },
  chainWebpack: config => {
    // 删除Vue CLI原先打包编译的一些无用功能
    config.optimization.delete('splitChunks') // 因为每个组件是独立打包,不需要抽离每个组件的公共js出来。
    config.plugins.delete('copy') // 不要复制public文件夹内容到lib文件夹中。
    config.plugins.delete('html') // 只打包组件,不生成html页面。
    config.plugins.delete('preload')
    config.plugins.delete('prefetch')
    config.plugins.delete('hmr')
    config.entryPoints.delete('app')
  }
}

module.exports = buildConfig

config/utils.js

const fs = require('fs')
const { join } = require('path')

const resolve = dir => join(__dirname, '../', dir)

/**
 * @desc 大写转横杠
 * @param {*} str
 */
function upperCasetoLine(str) {
  let temp = str.replace(/[A-Z]/g, function (match) {
    return '-' + match.toLowerCase()
  })
  if (temp.slice(0, 1) === '-') {
    temp = temp.slice(1)
  }
  return temp
}

/**
 * @desc 获取组件入口
 * @param {*} path
 */
function getComponentEntries(path) {
  let files = fs.readdirSync(resolve(path))

  const componentEntries = files.reduce((fileObj, item) => {
    //  文件路径
    const itemPath = join(path, item)
    //  在文件夹中
    const isDir = fs.statSync(itemPath).isDirectory()
    const [name, suffix] = item.split('.')

    //  文件中的入口文件
    if (isDir) {
      fileObj[`x-${upperCasetoLine(item)}`] = resolve(join(itemPath, 'index.js'))
    }
    //  文件夹外的入口文件
    else if (suffix === 'js') {
      fileObj[name] = resolve(`${itemPath}`)
    }
    return fileObj
  }, {})

  return componentEntries
}

module.exports = {
  resolve,
  upperCasetoLine,
  getComponentEntries
}

# 导出组件库

  • 组件目录

在 button 文件夹新增 index.js 文件,对外提供对组件的引用

├─packages
		├─button
		│		└─button.vue
		│		└─button.scss
		│		└─index.js  # button组件的入口
		├─...
		│
		│ index.js  # 所有组件的入口
  • button/index.js:单独提供 install 方法

需要注意的是:指令式组件有些许不同,如果 js 文件里已经有了 install 方法,就不用重复操作了。

import XButton from './button.vue'

XButton.install = function (Vue) {
  Vue.component(XButton.name, XButton)
}

export default XButton
  • packages/index.js:不仅要导出整个组件的 install 方法,还要导出每个组件的。
import XButton from './button/button.vue'

const components = [
  XButton,
  ...
]

// 全局注册组件
const install = Vue => {
  if (install.installed) return
  components.forEach(c => {
    Vue.use(c)
  })
}

/**
 * 有可能组件会通过script标签引入,如<script src='https://xxx/xqh-vue-ui'></script>
 */
if (typeof Window.Vue !== 'undefined') {
  install(Vue) // 全局直接通过script 引用的方式会默认调用install方法
}

export default {
  install,
  XButton,
  ...
}

# 打包组件库

  • 打包后的目录
.
├── index
│   ├── index.js
│   └── style.css
├── x-button
│   ├── index.js
│   └── style.css
├── ...

# 上传 npm

  • 配置 package.json 文件
{
  "name": "xqh-vue-ui",
  "version": "1.0.0",
  "private": false,
  "main": "lib/index/index.js",
  ...
}

# 导入组件库

  • 安装
npm install xqh-vue-ui
  • 按需引入(原生的)
import Vue from 'vue'
import XButton from 'xqh-vue-ui/lib/x-button'
import 'xqh-vue-ui/lib/x-button/style.css'
Vue.use(XButton)

还要知道单独组件的样式文件在哪里,麻烦!

  • 按需引入(借助插件)

babel-plugin-import:为组件库实现单组件按需加载并且自动引入其样式。

npm install babel-plugin-import

在 babel.config.js 中配置

  plugins: [
    [
      'import',
      {
        libraryName: 'xqh-vue-ui',
        style: name => {
          return `${name}/style.css`
        },
      }
    ]
  ]

使用

import Vue from 'vue'
import { XButton } from 'xqh-vue-ui'
Vue.use(XButton)