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_ENV
为development
。
//开发环境配置
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有两个好处
- 减少打包编译的时间;
- 避免在生产环境中用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/index/index.js",
复制代码
2、其他配置
七、按需引入配置
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选项的值。
如果不需按需加载需单独引入css样式:如,import 'mulit-npm/lib/style/index.css'