阅读 4500

Vue CLI3搭建组件库并实现按需引入实战操作

Vue CLI3搭建组件库并用npm发布实战操作中介绍了单个组件的组件库怎么开发,本文将一步一步教大家怎么开发多个组件集成的组件库,怎么实现组件库按需引入。觉得有用点个赞支持一下。

本文将略过安装Vue CLI3、搭建组件库的项目、清洁组件库项目的步骤,不懂的地方可以看Vue CLI3搭建组件库并用npm发布实战操作

一、建立两个git仓库放本文的示例代码

二、组件库项目中webpack一些基础配置

在Vue CLI3中,项目的webpack配置是要在根目录下新建vue.config.js来配置。

1、区分开发环境和生成环境的配置

因为多入口组件库中开发环境和生成环境的配置是不同,所有要区分开来。 通过process.env.NODE_ENV变量来判断,生产环境时process.env.NODE_ENVdevelopment

//开发环境配置
const devConfig = {
    //...
}
const buildConfig = {
    //...
}
module.exports = process.env.NODE_ENV === 'development' ? devConfig : buildConfig;
复制代码

2、更改src文件夹的名字

在Vue组件库项目中原来src文件夹的内容是demo展示的内容,所以文件名改成examples,比较形象。

3、重新配置项目入口文件

在vue.config.js文件中配置内容如下:

const devConfig={
    pages: {
        index: {
            entry: 'examples/main.js',
            template: 'public/index.html',
            filename: 'index.html',
        },
    },
}
复制代码

4、配置文件别名

文件别名会在写demo中用到。在vue.config.js文件中配置内容如下:

const path = require('path');
function resolve(dir) {
    return path.resolve(__dirname, dir)
}
const devConfig = {
    configureWebpack: {
        resolve: {
            extensions: ['.js', '.vue', '.json'],
            alias: {
                '@': resolve('packages'),
                'assets': resolve('examples/assets'),
                'views': resolve('examples/views'),
            }
        }, 
    },
}
复制代码

5、配置devServer项

在vue.config.js文件中配置内容如下:

const devConfig  = {
    devServer:{
        port: 8091,//固定端口
        hot: true,//开启热更新
        open: 'Google Chrome'//固定打开浏览器
    }
}
复制代码

6、在根目录下新建packages文件夹

组件的代码在packages文件夹中开发

7、将新增的packages文件夹加入babel转码编译

在vue.config.js文件中配置内容如下:

const devConfig = {
    chainWebpack: config => {
        config.module
            .rule('js')
            .include
            .add('/packages')
            .end()
            .use('babel')
            .loader('babel-loader')
            .tap(options => {
                return options
            })
    },
}
复制代码

以上是开发环境的配置,下面来写一下生产环境的配置

8、在生产环境下也要将新增的packages文件夹加入babel转码编译

const buildConfig = {
    chainWebpack: config => {
        config.module
            .rule('js')
            .include
            .add('/packages')
            .end()
            .use('babel')
            .loader('babel-loader')
            .tap(options => {
                return options
            })
    },
}
复制代码

9、配置生产环境构建文件的目录

我们将组件库打包编译后放在lib文件夹中,在vue.config.js文件中配置内容如下:

const buildConfig = {
    outputDir: 'lib',
}
复制代码

10、关闭source map

关闭source map有两个好处

  1. 减少打包编译的时间;
  2. 避免在生产环境中用F12开发者工具在Sources中看到源码。

在vue.config.js文件中配置内容如下:

const buildConfig = {
    productionSourceMap: false,
}
复制代码

三、多入口文件页面打包配置

在Vue CLI3搭建的项目中借助babel-plugin-import这个webpack插件并且配置babel.config.js,来实现组件库的按需引入的前提是组件库是多入口文件页面打包的。

1、配置entry

在Vue CLI3中是在configureWebpack选项的entry属性上配置项目多入口,在本文案例中,配置如下

const buildConfig = {
    configureWebpack: {
        entry: { 
            index: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\index.js',
            testA: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\testA\\index.js',
            testB: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\testB\\index.js' 
        },
    },
}
复制代码

但是以上每个入口都是写死的,所以我们要利用nodejs实现自动化配置。

首先我们引入nodejs中path模块来处理文件路径。

const path = require('path');
const join = path.join;//拼接路径
复制代码

写一个把目标路径按当前文件路径转成绝对路径的方法

function resolve(dir) {
    return path.resolve(__dirname, dir)
}
复制代码

其中__dirname是当前文件所在目录的完整绝对路径,例

还要引入nodejs中fs模块在处理文件信息

const fs = require('fs');
复制代码

我们建一个函数getEntries(path),其中path是组件代码所在的文件夹名称,返回一个对象entries,key为每个组件文件夹的名称,值为每个组件文件夹中入口文件index.js的绝对路径。

首先使用fs.readdirSync(resolve(path))获取到组件代码所在的文件夹目录下所有文件名称,存在files变量中。

然后用数组reduce()方法循环files,先将每个文件名(item)利用join(path, item)转成路径存到itemPath变量。

fs.statSync(itemPath).isDirectory()对每个文件进行判断是不是文件夹。

如果是文件夹,先把itemPath和入口文件index.js拼接成一个地址,再转成绝对路径,将item作为key,赋值到返回对象上

entries[item] = resolve(join(itemPath, 'index.js'))。
复制代码

如果不是文件夹,直接把itemPath转成绝对路径,将item去除后缀作为key,赋值到返回对象上

const [name] = item.split('.')
entries[name] = resolve(`${itemPath}`)
复制代码

下面是完整代码

function getEntries(path) {
    let files = fs.readdirSync(resolve(path));
    const entries = files.reduce((ret, item) => {
        const itemPath = join(path, item)
        const isDir = fs.statSync(itemPath).isDirectory();
        if (isDir) {
            ret[item] = resolve(join(itemPath, 'index.js'))
        } else {
            const [name] = item.split('.')
            ret[name] = resolve(`${itemPath}`)
        }
        return ret
    }, {})
    return entries
}
复制代码

比如在本文案例中,执行getEntries('packages')将会得到一个对象

{ 
    index: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\index.js',
    testA: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\testA\\index.js',
    testB: 'D:\\project\\02npm\\02Vue_Cli3_MAPLib\\packages\\testB\\index.js' 
},
复制代码

利用对象展开运算符,配置entry

const buildConfig = {
    configureWebpack: {
        entry: { 
            ...getEntries('packages'),
        },
    },
}
复制代码

2、配置output

  • filename: 配置每个组件打包后生成对应文件名称,多入口文件配置为[name].index.js,为什么配置这个名称后面会解释。
  • libraryTarget:配置为commonjs2,入口文件的返回值将分配给module.exports对象,使其组件库在webpack构建的环境下使用,这个是关键。
const buildConfig = {
    configureWebpack: {
        output: {
            filename: '[name]/index.js',
            libraryTarget: 'commonjs2',
        }
    },
}
复制代码

3、样式打包配置

在css.extract.filename上配置样式打包路径和文件名称

const buildConfig = {
    css: {
        sourceMap: true,
        extract: {
            filename: 'style/[name].css'//在lib文件夹中建立style文件夹中,生成对应的css文件。
        }
    },
}
复制代码

4、删除Vue CLI3原先打包编译的一些无用功能

  • 删除splitChunks,因为每个组件是独立打包,不需要抽离每个组件的公共js出来。
  • 删除copy,不要复制public文件夹内容到lib文件夹中。
  • 删除html,只打包组件,不生成html页面。
  • 删除preload以及prefetch,因为不生成html页面,所以这两个也没用。
  • 删除hmr,删除热更新。
  • 删除自动加上的入口App。
const buildConfig = {
    chainWebpack: config => {
        config.optimization.delete('splitChunks')
        config.plugins.delete('copy')
        config.plugins.delete('html')
        config.plugins.delete('preload')
        config.plugins.delete('prefetch')
        config.plugins.delete('hmr')
        config.entryPoints.delete('app')
    },
}
复制代码

5、配置字体的loader

const buildConfig = {
    chainWebpack: config => {
        config.module
            .rule('fonts')
            .use('url-loader')
            .tap(option => {
                option.fallback.options.name = 'static/fonts/[name].[hash:8].[ext]'
                return option
            })
    },
}
复制代码

四、组件库开发

本案例中简单写了testA和testB两个组件来供测试

1、packages目录结构

主要讲一下组件入口文件怎么编写,其它跟平时开发一样。

2、单个组件入口

以组件testA为例,对外暴露一个对象test,并提供 install 方法,外部就可以通过Vue.use()来调用这个组件。

import test from './src/index.vue';
test.install = function(Vue) {
    Vue.component(test.name, test);
};
export default test;
复制代码

3、组件库总入口

import testA from './testA'
import testB from './testB'
export default {
    install(Vue) {
        Vue.use(testA);
        Vue.use(testB)
    },
}
复制代码

五、编译打包

执行npm run build,打包编译后,在项目中会得到一个lib文件夹,内容如图所示。

六、npm发布前准备工作

1、配置主入口文件

在package.json文件中写入:

"main": "lib/tree-select.umd.min.js",
复制代码

2、其他配置

可以参考Vue CLI3搭建组件库并用npm发布实战操作

七、按需引入配置

1、引入组件

发布好后(如果不会发布请看Vue CLI3搭建组件库并用npm发布实战操作),在引用组件库demo中,执行npm install map-lib-test@0.6.0 --save安装组件库。

在执行npm install babel-plugin-import --save-dev安装babel-plugin-import插件,利用它实现组件按需引入。

在根目录下.babelrc.js文件中按如下配置

module.exports = {
    "presets": ["@vue/app"],
    "plugins": [
        [
            "import",
            {
                "libraryName": "map-lib-test",//组件库名称
                "camel2DashComponentName": false,//关闭驼峰自动转链式
                "camel2UnderlineComponentName": false//关闭蛇形自动转链式
            }
        ],
    ]
}

复制代码

在main.js写入

import { testA, testB } from 'map-lib-test';
Vue.use(testA);
Vue.use(testB);
复制代码

在src/views/demo.vue引用

<template>
    <div>
        <testA></testA>
        <testB></testB>
    </div>
</template>
<script>
export default {
    data() {
        return {
        }
    },
}
</script>
复制代码

浏览器展示

2、引入样式

在根目录下.babelrc.js文件中按如下配置

module.exports = {
    "presets": ["@vue/app"],
    "plugins": [
        [
            "import",
            {
                "libraryName": "map-lib-test",//组件库名称
                "camel2DashComponentName": false,//关闭驼峰自动转链式
                "camel2UnderlineComponentName": false//关闭蛇形自动转链式
                "style": (name) =>{
                    const cssName = name.split('/')[2];
                    return `map-lib-test/lib/style/${cssName}.css`
                }
            }
        ],
    ]
}

复制代码

style的值为函数时,babel-plugin-import将自动导入文件路径等于函数返回值的文件。组件库打包后的css文件的路径如下图所示。故如上述代码所配置。其中style的值为函数时,其参数name的值的示例为map-lib-test/lib/testA,使用时可以打印出来看一下。

浏览器展示

八、注意点

1、 为什么是import{testA,testB}而不是import{testC,testD}

如果是`import {testC ,testD }`,会发生如下报错
复制代码

import { testA, testB } from 'map-lib-test';
复制代码

相当

import testA from  "map-lib-test/lib/testA";
import testB from  "map-lib-test/lib/testB";
复制代码
import { testC, testD } from 'map-lib-test';
复制代码

相当

import testC from  "map-lib-test/lib/testC";
import testB from  "map-lib-test/lib/testD";
复制代码

map-lib-test/lib中没有testC、testD这个文件,所以就报以上错误。

那为什么是import{testA , testB},还记得在多入口文件页面打包配置中配置entry时,entry对象每个key是每个组件代码所在的文件夹名,而output.filename是'[name]/index.js'。每个组件是独立打包,打包生成文件夹名称是原来每个组件代码所在文件夹名称。

这样是不是很清楚,为什么要import{testA , testB},是因为testA和testB两组件打包生成文件夹名分别是testA和testB。

以上还可以说明为什么output.filename是'[name]/index.js'而不是'[name]/main.js'或者而不是'[name]/out.js'

因为import testA from "map-lib-test/lib/testA";默认相当import testA from "map-lib-test/lib/testA/index.js";

所以在写组件使用文档时,一定要写明按需引入时,是import{testA , testB}的。

2、组件标签

为什么是<testA></testA>,而不是<testD></testD>。 看一下testA组件的入口文件中有写这么一段代码

import test from './src/index.vue';
test.install = function(Vue) {
    Vue.component(test.name, test);
};
复制代码

在Vue中注册以test.name为名称的组件,而test.name就是testA组件中name选项的值。