青训营笔记-组件库开发(二)项目配置

113 阅读3分钟

安装vue vite unplugin-vue-define-options sass

安装css预处理器

pnpm add sass -D -w
pnpm add vite vue unplugin-vue-define-options -D -w

二、模块抽离

1、eslint-config抽离

一般市面上的组件库都会将eslint-config单独抽离出来

在packages下新建一个项目eslint-config

在packages/eslint-config的目录下初始化package.json

将根目录的package.json中的下面这些依赖移入到本项目中,根目录的package.json里的这些依赖就可以移除了,但需要保留eslint

{
    "name": "@kunlun-design/eslint-config",
    "version": "0.0.1",
    "description": "eslint-config",
    "main": "index.js",
    "files": [
        "index.js"
    ],
    "keywords": [
        "eslint",
        "tslint",
        "config",
        "kunlun-design"
    ],
    "author": "anixuil",
    "license": "ISC",
    "dependencies": {
        "@typescript-eslint/eslint-plugin": "^5.48.1",
        "@typescript-eslint/parser": "^5.48.1",
        "eslint-config-prettier": "^8.6.0",
        "eslint-plugin-prettier": "^4.2.1",
        "eslint-plugin-vue": "^9.9.0"
    }
}
​

packages/eslint-config下创建index.js,将根目录的.eslintrc.js的内容拷贝过来,把root属性删除了

module.exports = {
    parser: 'vue-eslint-parser',
    extends: [
        'eslint:recommended',
        'plugin:vue/vue3-essential',
        'plugin:@typescript-eslint/recommended',
        'prettier',
        'plugin:prettier/recommended'
    ],
    env: {
        browser: true,
        es2021: true,
        commonjs: true
    },
    plugins: ['@typescript-eslint', 'prettier', 'vue'],
    parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        parser: '@typescript-eslint/parser'
    },
    rules: {
        'prettier/prettier': 'error',
        'no-extra-semi': 'off',
        'no-undef': 'off', //未定义
        'vue/multi-word-component-names': 'off', //驼峰
        '@typescript-eslint/camelcase': 'off',
        '@typescript-eslint/ban-ts-ignore': 'off',
        '@typescript-eslint/no-var-requires': 'off',
        '@typescript-eslint/no-extra-semi': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-empty-function': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off',
        '@typescript-eslint/explicit-function-return-type': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        '@typescript-eslint/no-empty-interface': 'off'
    }
}
​

根目录的.eslintrc.js

module.exports = {
    root: true,
    extends: ['@kunlun-design/eslint-config'],
    ignorePatterns: ['!.*', 'node_modules']
}
​

在根目录的package.json添加@kunlun-design/eslint-config依赖

"@kunlun-design/eslint-config": "workspace:*"

最后在根目录pnpm install安装

2、utils工具包抽离

也不能叫抽离,毕竟还没写呢哈哈哈,先给他预留一个位置

在package里先建一个目录utils,然后初始化package.json

{
    "name": "@kunlun-design/utils",
    "version": "0.0.1",
    "private": true,
    "license": "MIT",
    "main": "index.ts",
    "peerDependencies": {
        "vue": "^3.2.45"
    }
}

packages/components/utils/component.ts

//组件类名的命名空间
type ClassName = string | undefined | null
type Classes = (ClassName | [any, ClassName, ClassName?])[]
export function createNamespace(name: string) {
    const namespace = `kl-${name}`
​
    //命名规范 suffix词尾 后缀
    const createBEM = (suffix?: string): string => {
        //如果没传那就是组件的名字 比如传个button进来 button是直接调用的createNamespace,所以这个方法就没传参,就直接返回namespace
        if (!suffix) return namespace
        return suffix.startsWith('--') ? `${namespace}${suffix}` : `${namespace}__${suffix}`
    }
​
    const classes = (...classes: Classes): any[] => {
        return classes.map(className => {
            if (Array.isArray(className)) {
                const [condition, truthy, falsy = null] = className
                return condition ? truthy : falsy
            }
​
            return className
        })
    }
​
    return {
        n: createBEM,
        classes
    }
}
​
// type:其作用就是给类型起一个新名字,可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型
// 所谓BEM,其实是三个单词的缩写:Block(模块)、Element(元素)、Modifier(修饰符)。

packages/components/utils/install.ts 这个我是照着别人写的,ts学的不行,看不太懂

import type { App, Plugin, AppContext } from 'vue'
export type SFCWithInstall<T> = T & Pluginexport type SFCInstallWithContext<T> = SFCWithInstall<T> & {
    _context: AppContext | null
}
​
export const withInstall = <T, E extends Record<string, any>>(main: T, extra?: E) => {
    ;(main as any).install = (app: App): void => {
        for (const comp of [main, ...Object.values(extra ?? {})]) {
            app.component(comp.name, comp)
        }
    }
​
    if (extra) {
        for (const [key, comp] of Object.entries(extra)) {
            ;(main as any)[key] = comp
        }
    }
    return main as SFCWithInstall<T> & E
}
​
export const withInstallFunction = <T>(fn: T, name: string) => {
    ;(fn as SFCWithInstall<T>).install = (app: App) => {
        ;(fn as SFCInstallWithContext<T>)._context = app._context
        app.config.globalProperties[name] = fn
    }
​
    return fn as SFCInstallWithContext<T>
}
​

packages/components/utils/index.ts

export * from './component'
export * from './install'

三、样式统一

为了统一风格和以后便于维护,以及让使用者轻松的实现自定义主题,在开始编写组件之前都会提前定义好一些css变量。我们会使用scss+css变量的方式

但我们首先得把stylelint装上

1、stylelint配置

pnpm add stylelint stylelint-scss stylelint-config-standard-vue stylelint-config-prettier stylelint-config-html stylelint-order postcss-html postcss-scss -D -w

在根目录创建 .stylelint.config.js

module.exports = {
    root: true,
    plugins: ['stylelint-order', 'stylelint-scss'],
    customSyntax: 'postcss-html',
    extends: [
        'stylelint-config-standard-vue',
        'stylelint-config-prettier',
        'stylelint-config-html'
    ],
    rules: {
        'selector-class-pattern': null,
        'selector-pseudo-class-no-unknown': [
            true,
            {
                ignorePseudoClasses: ['deep', 'global']
            }
        ],
        'selector-pseudo-element-no-unknown': [
            true,
            {
                ignorePseudoElements: ['v-deep', 'v-global', 'v-slottted']
            }
        ],
        'at-rule-no-unknown': [
            true,
            {
                ignoreAtRules: [
                    'tailwind',
                    'apply',
                    'variants',
                    'responsive',
                    'screen',
                    'function',
                    'if',
                    'each',
                    'include',
                    'mixin'
                ]
            }
        ],
        'no-empty-source': null,
        'named-grid-areas-no-invalid': null,
        'unicode-bom': 'never',
        'no-descending-specificity': null,
        'font-family-no-missing-generic-family-keyword': null,
        'declaration-colon-space-after': 'always-single-line',
        'declaration-colon-space-before': 'never',
        'rule-empty-line-before': [
            'always',
            {
                ignore: ['after-comment', 'first-nested']
            }
        ],
        'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
        'keyframes-name-pattern': null,
        'order/order': [
            [
                'dollar-variables',
                'custom-properties',
                'at-rules',
                'declarations',
                {
                    type: 'at-rule',
                    name: 'supports'
                },
                {
                    type: 'at-rule',
                    name: 'media'
                },
                'rules'
            ],
            { severity: 'warning' }
        ],
        'order/properties-order': [
            'position',
            'top',
            'right',
            'bottom',
            'left',
            'z-index',
            'display',
            'float',
            'width',
            'height',
            'max-width',
            'max-height',
            'min-width',
            'min-height',
            'padding',
            'padding-top',
            'padding-right',
            'padding-bottom',
            'padding-left',
            'margin',
            'margin-top',
            'margin-right',
            'margin-bottom',
            'margin-left',
            'margin-collapse',
            'margin-top-collapse',
            'margin-right-collapse',
            'margin-bottom-collapse',
            'margin-left-collapse',
            'overflow',
            'overflow-x',
            'overflow-y',
            'clip',
            'clear',
            'font',
            'font-family',
            'font-size',
            'font-smoothing',
            'osx-font-smoothing',
            'font-style',
            'font-weight',
            'hyphens',
            'src',
            'line-height',
            'letter-spacing',
            'word-spacing',
            'color',
            'text-align',
            'text-decoration',
            'text-indent',
            'text-overflow',
            'text-rendering',
            'text-size-adjust',
            'text-shadow',
            'text-transform',
            'word-break',
            'word-wrap',
            'white-space',
            'vertical-align',
            'list-style',
            'list-style-type',
            'list-style-position',
            'list-style-image',
            'pointer-events',
            'cursor',
            'background',
            'background-attachment',
            'background-color',
            'background-image',
            'background-position',
            'background-repeat',
            'background-size',
            'border',
            'border-collapse',
            'border-top',
            'border-right',
            'border-bottom',
            'border-left',
            'border-color',
            'border-image',
            'border-top-color',
            'border-right-color',
            'border-bottom-color',
            'border-left-color',
            'border-spacing',
            'border-style',
            'border-top-style',
            'border-right-style',
            'border-bottom-style',
            'border-left-style',
            'border-width',
            'border-top-width',
            'border-right-width',
            'border-bottom-width',
            'border-left-width',
            'border-radius',
            'border-top-right-radius',
            'border-bottom-right-radius',
            'border-bottom-left-radius',
            'border-top-left-radius',
            'border-radius-topright',
            'border-radius-bottomright',
            'border-radius-bottomleft',
            'border-radius-topleft',
            'content',
            'quotes',
            'outline',
            'outline-offset',
            'opacity',
            'filter',
            'visibility',
            'size',
            'zoom',
            'transform',
            'box-align',
            'box-flex',
            'box-orient',
            'box-pack',
            'box-shadow',
            'box-sizing',
            'table-layout',
            'animation',
            'animation-delay',
            'animation-duration',
            'animation-iteration-count',
            'animation-name',
            'animation-play-state',
            'animation-timing-function',
            'animation-fill-mode',
            'transition',
            'transition-delay',
            'transition-duration',
            'transition-property',
            'transition-timing-function',
            'background-clip',
            'backface-visibility',
            'resize',
            'appearance',
            'user-select',
            'interpolation-mode',
            'direction',
            'marks',
            'page',
            'set-link-source',
            'unicode-bidi',
            'speak'
        ]
    },
    ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts']
}
​

根目录新建一个 .stylelintrc.js

module.exports = {
    extends: 'stylelint-config-standard-vue',
    customSyntax: 'postcss-html',
    rules: {
        // 颜色值小写
        'color-hex-case': 'lower',
        // 注释前无须空行
        'comment-empty-line-before': 'never',
        // 使用数字或命名的 (可能的情况下) font-weight 值
        'font-weight-notation': null,
        // 在函数的逗号之后要求有一个换行符或禁止有空白
        'function-comma-newline-after': null,
        // 在函数的括号内要求有一个换行符或禁止有空白
        'function-parentheses-newline-inside': null,
        // url使用引号
        'function-url-quotes': 'always',
        // 字符串使用单引号
        'string-quotes': 'single',
        // 缩进
        indentation: 4,
        // 禁止低优先级的选择器出现在高优先级的选择器之后
        'no-descending-specificity': null,
        // 禁止空源
        'no-empty-source': null,
        // 禁止缺少文件末尾的换行符
        'no-missing-end-of-source-newline': null
    }
}
​

这里可能会遇到报错 有关stylelint-config-standard的,网上有解决方案

根目录创建 .stylelintingore

//这里写你想忽略的,如果没有可删除 比如我最终打包的目录叫lib 我肯定不希望它检查打包好的css文件,就可以把打包目录添加进去

2、样式编写

packages/compoenents下创建styles目录用于存放样式变量

我的组件样式统一管理实现思路是这样的(button组件举例)

  1. type.scss 一个专门管理类型样式的地方,用来管理primary,default等这些type的样式
  2. size.scss 一个专门管理类似 mini small normal等尺寸大小样式的地方
  3. common.scss 作为一个样式统一出口文件
  4. var.scss 声明样式变量的地方
  5. root.scss 根文件,自动配置组件样式的地方

packages/components/styles/root.scss

@import './type.scss';
@import './size.scss';
@import './variables.scss';
​
$components: button, input, select;
​
@function typed($type, $key) {
    @return map-get($type, $key);
}
​
@mixin kl-attribute($typeName, $attrKey, $attrName) {
    #{$attrKey}: typed($typeName, $attrName);
}
​
//复用匹配组件
@mixin matchComponent($typeKey) {
    @each $comKey, $comValue in $components {
        .kl-#{$comKey}--#{$typeKey} {
            @content;
        }
    }
}
​
:root {
    //type类型判断
    @each $typeKey, $typeValue in $typeStyle {
        @include matchComponent($typeKey) {
            // box-sizing: border-box;
            border-radius: 5px;
            //button
            @include kl-attribute($typeValue, '--kl-bg-color', 'kl-bg');
            @include kl-attribute($typeValue, '--kl-font-color', 'kl-fontColor');
            @include kl-attribute($typeValue, '--kl-border', 'kl-border');
        }
    }
​
    //size
    @each $sizeKey, $sizeValue in $sizeStyle {
        @include matchComponent($sizeKey) {
            @include kl-attribute($sizeValue, '--kl-size-width', 'kl-width');
            @include kl-attribute($sizeValue, '--kl-size-height', 'kl-height');
            @include kl-attribute($sizeValue, '--kl-size-font', 'kl-fontSize');
        }
    }
}
​

packages/components/styles/type.scss

@import './var.scss';
//type样式管理
$typeStyle: (
    'primary': (
        kl-bg: $primary-bgColor,
        kl-fontColor: $primary-fontColor,
        kl-border: $primary-border,
        kl-bg-active: $primary-bgColor-active
    ),
    'success': (
        kl-bg: $success-bgColor,
        kl-fontColor: $success-fontColor,
        kl-border: $success-border,
        kl-bg-active: $success-bgColor-active
    ),
    'info': (
        kl-bg: $info-bgColor,
        kl-fontColor: $info-fontColor,
        kl-border: $info-border,
        kl-bg-active: $info-bgColor-active
    ),
    'warning': (
        kl-bg: $warning-bgColor,
        kl-fontColor: $warning-fontColor,
        kl-border: $warning-border,
        kl-bg-active: $warning-bgColor-active
    ),
    'danger': (
        kl-bg: $danger-bgColor,
        kl-fontColor: $danger-fontColor,
        kl-border: $danger-border,
        kl-bg-active: $danger-bgColor-active
    ),
    'default': (
        kl-bg: $default-bgColor,
        kl-fontColor: $default-fontColor,
        kl-border: $default-border,
        kl-border-active: $default-border-active
    )
);
​

packages/components/styles/size.scss

@import './var.scss';
//size样式管理
$sizeStyle: (
    'mini': (
        kl-fontSize: $mini-fontSize,
        // kl-paddingSize: $mini-paddingSize,
        kl-width: $mini-width,
        kl-height: $mini-height
    ),
    'small': (
        kl-fontSize: $small-fontSize,
        // kl-paddingSize: $small-paddingSize,
        kl-width: $small-width,
        kl-height: $small-height
    ),
    'normal': (
        kl-fontSize: $normal-fontSize,
        // kl-paddingSize: $normal-paddingSize,
        kl-width: $normal-width,
        kl-height: $normal-height
    ),
    'large': (
        kl-fontSize: $large-fontSize,
        // kl-paddingSize: $large-paddingSize,
        kl-width: $large-width,
        kl-height: $large-height
    ),
    'xlarge': (
        kl-fontSize: $xlarge-fontSize,
        // kl-paddingSize: $xlarge-paddingSize,
        kl-width: $xlarge-width,
        kl-height: $xlarge-heigth
    )
);
​

packages/components/styles/var.scss

//样式变量统一声明
​
//全局通用样式
​
//字体
$font-color-white: white;
​
//边框
$border-none: none;
​
//过渡
$transition-quick: 0.1s ease-in;
$transition-normal: 0.3s ease;
$transition-slow: 0.5s ease;
​
// type
// primary
$primary-bgColor: #3a7afe;
$primary-bgColor-active: #075aff;
$primary-fontColor: $font-color-white;
$primary-border: $border-none;
​
//success
$success-bgColor: rgb(103, 194, 58);
$success-bgColor-active: rgb(47, 141, 0);
$success-fontColor: $font-color-white;
$success-border: $border-none;
​
//info
$info-bgColor: #85878b;
$info-bgColor-active: rgb(117, 118, 121);
$info-fontColor: $font-color-white;
$info-border: $border-none;
​
//warning
$warning-bgColor: rgb(230, 162, 60);
$warning-bgColor-active: rgb(170, 115, 32);
$warning-fontColor: $font-color-white;
$warning-border: $border-none;
​
//danger
$danger-bgColor: rgb(245, 108, 108);
$danger-bgColor-active: rgb(190, 62, 62);
$danger-fontColor: $font-color-white;
$danger-border: $border-none;
​
//default
$default-bgColor: white;
$default-fontColor: rgb(59, 59, 59);
$default-border: 1px solid $default-fontColor;
$default-border-active: 1px solid $primary-bgColor-active;
​
//size
//mini
$mini-fontSize: 12px;
// $mini-paddingSize: 5px 10px;
$mini-width: 55px;
$mini-height: 26px;
​
//small
$small-fontSize: 14px;
// $small-paddingSize: 10px 15px;
$small-width: 65px;
$small-height: 36px;
​
//normal
$normal-fontSize: 16px;
// $normal-paddingSize: 10px 20px;
$normal-width: 86px;
$normal-height: 40px;
​
//large
$large-fontSize: 18px;
// $large-paddingSize: 10px 20px;
$large-width: 95px;
$large-height: 43px;
​
//xlarge
$xlarge-fontSize: 20px;
// $xlarge-paddingSize: 10px 20px;
$xlarge-width: 100px;
$xlarge-heigth: 45px;
​

packages/components/styles/variables.scss

//样式变量声明
@import './var.scss';
​
:root {
    //基本变量
    //背景
    --kl-bg-color: white;
    //字体
    --kl-font-color: rgb(59, 59, 59);
    //边框
    --kl-border-color: rgb(59, 59, 59);
    --kl-border-width: 1px;
    --kl-border-style: solid;
    --kl-border: var(--kl-border-width) var(--kl-border-style) var(--kl-border-color);
    //尺寸
    --kl-size-width: #{$normal-width};
    --kl-size-height: #{$normal-height};
    --kl-size-font: #{$normal-fontSize};
}
​

packages/components/styles/common.scss

@import './root.scss';
​

packages/components/index.ts

import './styles/common.scss'