jak-vui v0.0.45
vuetily2.0
Project setup
npm install
Compiles and minifies for production
npm run build
Customize configuration
vue cli3 库项目搭建流程
- 初始化
vue cli3
项目 - 改装成库项目
- 发布
npm
- 在
CSR
与SSR
项目安装使用 - 各种等级的组件引入 (全局, 按需与组件级别按需)
scss
变量的引入和使用
支持完整引入 按需引入等多个引入方式, 支持 scss 变量的引用与覆盖
一、改造成库项目
在 Vue CLI3 中,项目的 webpack 配置是要在根目录下新建 vue.config.js 来配置。
1、区分开发环境和生成环境的配置
因为多入口组件库中开发环境和生成环境的配置是不同,所有要区分开来。 通过 process.env.NODE_ENV 变量来判断,生产环境时 process.env.NODE_ENV 为 development。
// vue.config.js
// 开发环境配置
const devConfig = {
// ...
}
const buildConfig = {
// ...
}
module.exports = process.env.NODE_ENV === 'development' ? devConfig : buildConfig
2、更改 src 文件夹的名字
在 Vue 组件库项目中原来 src 文件夹的内容是 demo 展示的内容,所以文件名改成 examples,比较形象。
3、重新配置项目入口文件
在 vue.config.js 文件中配置内容如下
// vue.config.js
const devConfig = {
pages: {
index: {
entry: 'examples/main.js',
template: 'public/index.html',
filename: 'index.html'
}
}
}
4、配置文件别名
文件别名会在写 demo 中用到(也可以直接使用相对路径)。在 vue.config.js 文件中配置内容如下:
// 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, // 开发环境服务端口
liveReload: false, // 热更新 这里mac系统有问题所以关闭
open: 'Google Chrome' // 调试浏览器
}
}
6、组织打包目录与组件目录
- 新建
packages
文件夹 这里存放需要编译的组件的index.js
方便导出以编译 - 新建
src
文件夹,这里放组件库的所有文件, 编写组件、样式和指令,方便组织各模块 src
下的组件库统一导出到package
下组织编译逻辑
7、babel 转码编译
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 转码编译
// vue.config.js
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 文件中配置内容如下:
// 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 属性上配置项目多入口,在本文案例中,配置如下
// vue.config.js
const buildConfig = {
configureWebpack: {
entry: {
index: 'D:\\workplace\\vuetily-2.0\\packages\\index.js',
VApp: 'D:\\workplace\\vuetily-2.0\\packages\\VApp\\index.js',
VBtn: 'D:\\workplace\\vuetily-2.0\\packages\\VBtn\\index.js'
}
}
}
但是以上每个入口都是写死的,不可能每个组件都来这里写一便,所以我们要利用 nodejs 实现自动化配置。
首先我们引入 nodejs 中 path 模块来处理文件路径。
const path = require('path')
const join = path.join //拼接路径
写一个把目标路径按当前文件路径转成绝对路径的方法
function resolve(dir) {
return path.resolve(__dirname, dir)
}
其中__dirname 是当前文件所在目录的完整绝对路径(物理路径),例'D:\workplace\vuetily-2.0'
还要引入 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:\\workplace\\vuetily-2.0\\packages\\index.js',
VApp: 'D:\\workplace\\vuetily-2.0\\packages\\VApp\\index.js',
VBtn: 'D:\\workplace\\vuetily-2.0\\packages\\VBtn\\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、样式打包配置
需安装 sass
编译器
npm i sass@1.26.10 sass-loader@9.0.3 -D
在 css.extract.filename
上配置样式打包路径和文件名称
如 /VBtn/VBtn.scss
编译生成到 /lib/style/VBtn.css
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
})
}
}
三、组件库开发
重复一遍 组件库开发相关的源代码在 src 文件夹下,packages 文件夹是用来导出组件,组织编译入口的
这里拿 VBtn
为例
1、src 库代码目录以及 packages 编译入口目录结构
-|src
--|styles
---|main.sass # 总样式
--|icon # 集成字体图标
---|iconfont.css
---|iconfont.woff2
--|VBtn
---|index.js # 组件js逻辑代码
---|VBtn.scss # 组件样式
--|VIcon
---|index.js
---|VIcon.scss
-|packages
--|bundle-style
---|index.js # 总样式/字体图标编译入口
--|Vbtn
---|index.js # 单个组件编译入口
--|VIcon
---|index.js
--|index.js # 组件库总编译入口
2、单个组件编译入口
// packages/VBtn
import VBtn from '../../src/VBtn' // 导入组件
// 定义单个组件install方法 并非真正注册 用到的时候使用 Vue.use() 注册
VBtn.install = function(Vue) {
Vue.component(VBtn.name, VBtn)
}
// 单个组件导出
export default VBtn
3、组件库样式/字体图标编译入口
在 src 目录下组织好要编译的 main.scss
总样式文件和 iconfont.css
样式文件,iconfont.css
里导入了依赖的图标字体 iconfont.woff2
// packages/bundle-style
import '../../src/styles/main.sass'
import '../../src/icon/iconfont.css'
export default {}
4、组件库总编译入口
// packages/index.js
import bundleStyle from './bundle-style' // 总样式与iconfont
import VBtn from './VBtn'
import VIcon from './VIcon'
const components = [VIcon, VBtn]
// 注册所有组件方法
const useComponents = Vue => {
components.map(component => Vue.component(component.name, component))
}
// 提供给Vue.use的install方法
const install = function(Vue) {
if (install.installed) return
useComponents(Vue)
}
// 第一个install是安装总库,后续的是单个组件导出
export default {
install,
VBtn,
VIcon
}
四、最终编译
执行 npm run build,打包编译后,在项目中会得到一个 lib 文件夹
style/bundle-style.css
总样式 不包括各个组件的样式 给按需引入时使用style/index.css
总样式 包括各个组件的样式 给总体引入时使用style/VBtn.css
单个组件样式 按需引入时需配合babel-import
编译 后面详说
-|lib/
--|index.js # 所有组件 总体引入
--|static/
---|fonts/
----|iconfont.[hash].woff2 # 图标字体
--|style/
---|bundle-style.css # 总样式 不包括各个组件的样式
---|index.css # 总样式 包括各个组件的样式
---|VBtn.css # 单个组件样式
---|VIcon.css
--|VBtn/
---|index.js # 单个组件 按需引入
--|VIcon/
---|index.js
五、发布到 npm
npm 账号注册激活邮箱步骤省略
配置 package.json
库名称 版本号 入口路径
{
"name": "jak-vui",
"version": "0.0.33",
"main": "lib/index/index.js"
}
六、在项目里安装使用
npm i jak-vui
1、完整引入
vuecli3:
// main.js
import Vue from 'vue'
import vuetily from 'jak-vui'
import 'jak-vui/style/index.css' // 总样式与字体图标
Vue.use(vuetily) // 安装整个库
NuxtJs:先定义插件
// plugins/vuetily.js
import Vue from 'vue'
import vuetily from 'jak-vui'
import 'jak-vui/style/index.css' // 总样式与字体图标
export default () => {
Vue.use(vuetily) // 安装整个库
}
再全局引入
// nuxt.config.js
module.exports = {
plugins: [{ src: '@/plugins/vuetily.js', ssr: true }]
}
2、全局按需引入
vuecli3:需使用 babel-plugin-import
开发依赖, 解析模块与渲染单个组件的样式
- 安装
babel-plugin-import
npm i babel-plugin-import -D
- 项目根目录新建
babel.config.js
style 的值为函数时,babel-plugin-import 将自动导入文件路径等于函数返回值的文件。组件库打包后的 css 文件的路径如下图所示。故如上述代码所配置。其中 style 的值为函数时,其参数
name
的值的示例为 vuetily/lib/VBtn
module.exports = {
presets: ['@vue/app'],
plugins: [
[
'import',
{
libraryName: 'jak-vui', // 组件库名称
camel2DashComponentName: false, // 关闭驼峰自动转链式
camel2UnderlineComponentName: false, // 关闭蛇形自动转链式
style: name => {
const cssName = name.split('/')[2]
return `jak-vui/lib/style/${cssName}.css`
}
}
]
]
}
- 引入的地方, 如
main.js
// main.js
import Vue from 'vue'
import { VBtn, VIcon } from 'jak-vui'
import 'jak-vui/style/bundle-style.css' // 不包含组件样式的总样式与字体图标
Vue.use(VBtn) // 安装单个组件 单组件样式通过babel编译
Vue.use(VIcon)
NuxtJs:需使用 babel-plugin-import
开发依赖, 解析模块与渲染单个组件的样式
- 安装
babel-plugin-import
npm i babel-plugin-import -D
nuxt.config.js
配置 babel
// nuxt.config.js
module.exports = {
build: {
babel: {
plugins: [
[
'import',
{
libraryName: 'jak-vui', // 组件库名称
camel2DashComponentName: false, // 关闭驼峰自动转链式
camel2UnderlineComponentName: false, // 关闭蛇形自动转链式
style: name => {
const cssName = name.split('/')[2]
return `jak-vui/lib/style/${cssName}.css`
}
}
]
]
}
}
}
- 定义插件
plugins/vuetily.js
// plugins/vuetily.js
import Vue from 'vue'
import { VBtn, VIcon } from 'jak-vui'
import 'jak-vui/style/bundle-style.css' // 不包含组件样式的总样式与字体图标
export default () => {
Vue.use(VBtn) // 安装单个组件 单组件样式通过babel编译
Vue.use(VIcon)
}
- 再全局引入
// nuxt.config.js
module.exports = {
plugins: [{ src: '@/plugins/vuetily.js', ssr: true }]
}
3、组件级别的按需引入
最细级别的按需引入, 适合 ssr 多页程序, 每个页面仅加载需要的组件. spa 程序没这个必要
总样式的话还是需要全局引入
<!-- component -->
<script>
import { VBtn, VIcon } from 'jak-vui'
export default {
components: { VBtn, VIcon }
}
</script>
4、scss/sass变量引用或覆盖
::: warning
必须安装 scss/sass
编译器
:::
npm i sass@1.26.10 sass-loader@9.0.3 -D
所有可用变量查看 开发文档
新建一个 scss/sass
文件
// vuetily.scss
@import 'jak-vui/src/styles/styles.sass' // 所有变量与方法
要覆盖变量仅需在引用框架变量后声明一个同样命名的变量再根据需要赋值即可
// vuetily.scss
@import 'jak-vui/src/styles/styles.sass' // 所有变量与方法
// 覆盖 $primary 和 $red 变量
$primary: map-deep-merge(
(
'base': #173761,
'sub': #034ea1
)
);
$red: map-deep-merge(
(
'base': #da291c,
'sub': #e64545
)
);
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago