14

Vue3.0已经发布了很长一段时间了,最近也在想着怎么去做工程化的东西,于是就想自己弄一个组件库捎带深入学习Vue3.0,于是便有了今天的这篇文章。

Git地址:https://github.com/ZhQuella/AzUIFront

技术栈

包管理工具:

yarn

开发模块:

Vite
Vue 3.0

打包工具:

Gulp
Rollup

单元测试:

Jest

语法工具:

ESLint

语言:

TypeScript

为什么要使用Yarn

看到包管理工具使用的事yarn或许很多小伙伴不太能理解为什么要使用yarn,说到这里可能就需要了解一下前端的工程化的概念了,什么是前端的工程化?工程化分别为模块化组件化规范化自动化

模块化

模块化是指将一个文件拆分成多个相互依赖的文件,最后进行统一的打包和加载,这样能够很好的保证高效的多人协作。
JavaScript模块化:CommonJSAMDCMD以及ES6 Module
CSS模块化:SassLessStylusBEMCssModules等。其中预处理器和 BEM 都会有的一个问题就是样式覆盖。
资源模块化:任何资源都能以模块的形式进行加载,目前大部分项目中的文件、CSS、图片等都能直接通过 JS 做统一的依赖关系处理

组件化

不同于模块化,模块化是对文件、对代码和资源拆分,而组件化则是对UI层面的拆分。
组件化并不是前端所特有的,一些其他的语言或者桌面程序等,都具有组件化的先例。确切的说,只要有UI层的展示,就必定有可以组件化的地方。简单来说,组件就是将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的细想就是组件化。不难看出,组件化设计就是为了增加复用性,灵活性,提高系统设计,从而提高开发效率。
组件系统是一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。我们会需要对页面进行拆分,将其拆分成一个一个的零件,然后分别去实现这一个个零件,最后再进行组装。
组件化的抽象设计是很重要的,不仅增加了复用性提高了工作效率,从某种程度上来说也反应了程序员对业务和产品设计的理解,一旦有问题或者需要功能扩展时,你就会发现之前的设计是多么的有意义。

规范化

规范化指的是我们在工程开发初期以及开发期间制定的系列规范,其中又包含了:

  1. 项目目录结构
  2. 对于编码这块的约束,一般我们都会采用一些强制措施,比如ESLintStyleLint等。
  3. 联调规范
  4. 文件命名规范
  5. 样式管理规范:目前流行的样式管理有BEMSassLessStylusCSS Modules等方式
  6. 工作流:其中包含分支命名规范、代码合并规范等
  7. 定期代码审查
自动化

从最早先的gruntgulp等,再到目前的webpackparcel。这些自动化工具在自动化合并、构建、打包都能为我们节省很多工作。而这些只是前端自动化其中的一部分,前端自动化还包含了持续集成、自动化测试等方方面面。


想必大家对与工程化也有了一定的了解,那么会过头来继续说为什么要使用yarn,上面说到了模块化如果我们想一下,组件的开发与组件文档的展示和组件库的本身是存在一切关联的,但是关联性并没有那么大,可以使用yarn的工作空间来分隔他们使他们本事作为一个独立的个体而存在,yarn工作流与npm类似都是使用package.json文件,yarn会重组node_modules文件,且不会与npm冲突。yarn的工作空间能够更好的帮助我们管理多个项目,可以在多个子项目中使用独立的package.json管理项目依赖。yarn会根据就依赖关系帮助你分析所有子项目的共用依赖,保证所有的项目公用的依赖只会被下载和安装一次。

初始化项目

初始化创建项目:

mkdir AzUiFont
cd AzUiFont
yarn init -y

创建.gitignore

node_modules
coverage
dist
es
lib

package-lock.json

修改package.json

{
  "name": "az-ui-font",
  "private": true,
  "version": "0.0.1",
  "main": "index.js",
  "license": "MIT"
}

搭建初始目录结构:

├─packages          //  项目总目录
│  ├─azui               //  组件库目录
│  ├─docs               //  展示文档目录
│  └─play               //  组件库开发展示目录
├─script            //  运行脚本
├─.gitignore       
├─index.html       
├─package.json     
├─README.md        
└─tsconfig.json
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AzUIFront</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

tsconfig.json

{
  "compilerOptions": {
    "rootDir": ".",
    "sourceMap": false,
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "moduleResolution": "node",
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "lib": ["esnext", "dom"],
    "types": ["jest", "node"]
  }
}

代码规范约束

这里使用eslint对代码规范进行约束,安装如下工具:

yarn add eslint -D -W
yarn add eslint-formatter-pretty -D -W
yarn add eslint-plugin-json -D -W
yarn add eslint-plugin-prettier -D -W
yarn add eslint-plugin-vue -D -W
yarn add @vue/eslint-config-prettier -D -W
yarn add babel-eslint -D -W
yarn add prettier -D -W

添加.eslintrc.js

module.exports = {
    "root": true,
    "env": {
        "browser": true,
        "es2020": true,
        "node": true,
        "jest": true
    },
    "globals": {
        "ga": true,
        "chrome": true,
        "__DEV__": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        'plugin:json/recommended',
        'plugin:vue/vue3-essential',
        '@vue/prettier'
    ],
    "parserOptions": {
        "ecmaVersion": 12,
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    "rules": {
    }
};

添加.eslintignore

*.sh
*.md
*.ico
*.css
*.md
*.scss
*.woff
*.ttf
*/*/shims.d.ts 
*/*/index.html
*/*/yarn-error.log
packages/azui/rollup-plugin-vue/index.d.ts
node_modules
lib
coverage
dist

package.json添加如下命令:

package.json

{
  "scripts": {
    "lint": "eslint --no-error-on-unmatched-pattern --ext .vue --ext .ts --ext .tsx packages/**/ --fix"
  },
  "devDependencies": {
    "eslint": "^7.24.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-formatter-pretty": "^4.0.0",
    "eslint-plugin-jest": "^24.3.5",
    "eslint-plugin-json": "^2.1.2",
    "eslint-plugin-prettier": "^3.4.0",
    "prettier": "^2.2.1"
  }
}

代码约束部分已经处理完成,接下来处理组件部分

组件库部分

执行命令

cd .\packages\azui\
yarn init -y

package.json

{
  "name": "azui",
  "version": "0.0.1",
  "private": true,
  "license": "MIT"
}

目录结构如下

├─rollup-plugin-vue
└─src
    ├─packages
    │  └─Button
    │      ├─Button.vue
    │      ├─index.ts
    │      └─__tests__
    └─styles

集成Babel到项目中,需要安装如下依赖:

yarn add babel -D -W
yarn add babel-plugin-syntax-dynamic-import -D -W
yarn add babel-plugin-syntax-jsx -D -W
yarn add babel-preset-env -D -W
yarn add @babel/plugin-proposal-optional-chaining -D -W
yarn add @babel/preset-env -D -W
yarn add @vue/babel-plugin-jsx -D -W

添加.babelrc

{
  "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]],
  "plugins": [
    "syntax-dynamic-import",
    ["@vue/babel-plugin-jsx"],
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-nullish-coalescing-operator"
  ],
  "env": {
    "utils": {
      "presets": [
        [
          "env",
          {
            "loose": true,
            "modules": "commonjs",
            "targets": {
              "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
            }
          }
        ]
      ],
      "plugins": [
        [
          "module-resolver",
          {
            "root": ["azui"],
            "alias": {
              "azui/src": "az-ui/lib"
            }
          }
        ]
      ]
    },
    "test": {
      "plugins": ["istanbul"],
      "presets": [["env", { "targets": { "node": "current" } }]]
    },
    "esm": {
      "presets": [["@babel/preset-env", { "modules": false }]]
    }
  }
}

集成自动化测试(单元测试)

yarn add jest -D -W
yarn add vue-jest@5.0.0-alpha.5 -D -W
yarn add babel-jest    -D -W                        
yarn add @vue/compiler-sfc@3.0.2 -D -W
yarn add @vue/test-utils@next -D -W
yarn add typescript -D -W

添加jest.config.js

module.exports = {
  testEnvironment: "jsdom", // 默认JSdom
  roots: ["<rootDir>/src/packages"], //
  transform: {
    "^.+\\.vue$": "vue-jest", // vue单文件
    "^.+\\js$": "babel-jest", // esm最新语法 import
  },
  moduleFileExtensions: ["vue", "js", "json", "jsx", "ts", "tsx", "node"],
  testMatch: ["**/__tests__/**/*.spec.js"],
  // 别名
  moduleNameMapper: {
    "^azui(.*)$": "<rootDir>$1",
    "^main(.*)$": "<rootDir>/src$1",
  },
};

package.json

{
  "scripts": {
    "test": "jest --runInBand"
  }
}

样式打包,添加如下依赖:

yarn add gulp -D -W
yarn add gulp-autoprefixer -D -W
yarn add gulp-sass -D -W
yarn add gulp-cssmin -D -W
yarn add cp-cli -D -W
yarn add tslib -D -W

package.json

{
  "scripts": {
    "build:theme": "gulp build --gulpfile gulpfile.js"
  }
}

使用Rollup打包组件,安装如下依赖:

yarn add rollup -D -W
yarn add rollup-plugin-peer-deps-external -D -W
yarn add rollup-plugin-scss -D -W
yarn add rollup-plugin-terser -D -W
yarn add rollup-plugin-vue -D -W
yarn add @rollup/plugin-node-resolve -D -W
yarn add @rollup/plugin-commonjs -D -W
yarn add @rollup/plugin-json -D -W
yarn add @rollup/plugin-replace -D -W
yarn add @rollup/plugin-babel -D -W
yarn add rollup-plugin-vue -D -W
yarn add rollup-plugin-typescript2 -D -W

添加rollup.config.js

import path from "path";
import scss from "rollup-plugin-scss";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import replace from "@rollup/plugin-replace";
import babel from "@rollup/plugin-babel";
import { terser } from "rollup-plugin-terser";
import ts from "rollup-plugin-typescript2";

import pkg from "./package.json";

const vuePlugin = require("./rollup-plugin-vue/index");
const getPath = (_path) => path.resolve(__dirname, _path);

const name = "AzUi";

const createBanner = () => {
  return `/*!
  * ${pkg.name} v${pkg.version}
  * (c) ${new Date().getFullYear()} Aaron
  * @license MIT
  */`;
};

const extensions = [".js", ".ts", ".tsx", ".scss"];

const tsPlugin = ts({
  tsconfig: getPath("./tsconfig.json"),
  extensions,
});

const createBaseConfig = () => {
  return {
    input: "src/index.ts",
    external: ["vue"],
    plugins: [
      peerDepsExternal(),
      babel(),
      resolve({
        extensions,
      }),
      commonjs(),
      json(),
      tsPlugin,
      vuePlugin({
        css: true
      }),
      scss({
        output: process.env.NODE_ENV === 'development'?
                  './dist/lib/index.css':
                  false,
        watch: ["./src/styles"]
      })
    ],
    output: {
      sourcemap: false,
      banner: createBanner(),
      externalLiveBindings: false,
      globals: {
        vue: "Vue"
      }
    }
  };
};

function mergeConfig(baseConfig, configB) {
  const config = Object.assign({}, baseConfig);
  // plugin
  if (configB.plugins) {
    baseConfig.plugins.push(...configB.plugins);
  }

  // output
  config.output = Object.assign({}, baseConfig.output, configB.output);

  return config;
}

function createFileName(formatName) {
  return `dist/az-ui.${formatName}.js`;
}

// es-bundle
const esBundleConfig = {
  plugins: [
    replace({
      __DEV__: `(process.env.NODE_ENV !== 'production')`,
    }),
  ],
  output: {
    file: createFileName("esm-bundler"),
    format: "es",
  },
};

// es-browser
const esBrowserConfig = {
  plugins: [
    replace({
      __DEV__: true,
    }),
  ],
  output: {
    file: createFileName("esm-browser"),
    format: "es",
  },
};

// es-browser.prod
const esBrowserProdConfig = {
  plugins: [
    terser(),
    replace({
      __DEV__: false,
    }),
  ],
  output: {
    file: createFileName("esm-browser.prod"),
    format: "es",
  },
};

// cjs
const cjsConfig = {
  plugins: [
    replace({
      __DEV__: true,
    }),
  ],
  output: {
    file: createFileName("cjs"),
    format: "cjs",
  },
};
// cjs.prod
const cjsProdConfig = {
  plugins: [
    terser(),
    replace({
      __DEV__: false,
    }),
  ],
  output: {
    file: createFileName("cjs.prod"),
    format: "cjs",
  },
};

// global
const globalConfig = {
  plugins: [
    replace({
      __DEV__: true,
      "process.env.NODE_ENV": true,
    }),
  ],
  output: {
    file: createFileName("global"),
    format: "iife",
    name
  },
};
// global.prod
const globalProdConfig = {
  plugins: [
    terser(),
    replace({
      __DEV__: false,
    }),
  ],
  output: {
    file: createFileName("global.prod"),
    format: "iife",
    name
  },
};

const formatConfigs = [
  esBundleConfig,
  esBrowserProdConfig,
  esBrowserConfig,
  cjsConfig,
  cjsProdConfig,
  globalConfig,
  globalProdConfig,
];

function createPackageConfigs() {
  return formatConfigs.map((formatConfig) => {
    return mergeConfig(createBaseConfig(), formatConfig);
  });
}

export default createPackageConfigs();

package.json

{
  "scripts": {
    "build": "rollup -c"
  }
}

组件库部分已经完成,接下来配置文档部分:

文档部分

执行命令

cd ../..
cd .\packages\docs\

创建目录结构:

├─public
├─scripts
└─src
    ├─assets
    ├─components
    └─__docs__

安装如下依赖:

yarn add @vitejs/plugin-vue -D -w
yarn add markdown-it-containe -D -w
yarn add node-sass -D -w
yarn add sass -D -w
yarn add sass-loader -D -w
yarn add vite -D -w
yarn add vite-plugin-vuedoc -D -w
yarn add vue@next -S -W
yarn add vue-router@4 -S -W

package.json

{
  "name": "@azui/docs",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "tsrv test"
  },
  "dependencies": {
    "azui": "0.0.1",
    "vue": "^3.0.7",
    "vue-router": "^4.0.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.1.5",
    "markdown-it-container": "^3.0.0",
    "node-sass": "^5.0.0",
    "sass": "^1.32.11",
    "sass-loader": "^11.0.1",
    "vite": "^2.0.5",
    "vite-plugin-vuedoc": "^3.1.2"
  }
}

添加vite.config.ts

import { defineConfig } from "vite";

import { createPlugin, vueDocFiles } from "vite-plugin-vuedoc";
import markdownItContainer from "markdown-it-container";
import vue from "@vitejs/plugin-vue";

import vitePluginSyncmd from "./scripts/vitePluginSyncmd";

const containers = ["success", "warning", "info", "error"].map((type) => {
  return [
    markdownItContainer,
    type,
    {
      validate: function (params: string) {
        const str = params.trim();
        if (str === type || str.startsWith(`${type} `)) {
          return [str, str === type ? "" : str.substr(type.length + 1)];
        }
        return null;
      },
      render: function (tokens: any[], idx: number) {
        const str = tokens[idx].info.trim();
        const m = [str, str === type ? "" : str.substr(type.length + 1)];
        if (tokens[idx].nesting === 1) {
          // opening tag
          return `<p>${type}--${m[1]}`;
        } else {
          // closing tag
          return "</p>";
        }
      },
    },
  ];
});

export default defineConfig({
  server: {
    port: 3000,
  },
  assetsInclude: ["src/assets"],
  optimizeDeps: {
    exclude: ["azui"],
  },
  plugins: [
    vitePluginSyncmd(),
    createPlugin({
      markdownIt: {
        plugins: [...containers],
      },
      highlight: {
        theme: "one-dark",
      },
    }),
    vue({
      include: [...vueDocFiles],
    }),
  ],
});

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "lib": ["esnext", "dom"],
    "types": ["vite/client"],
    "baseUrl": "."
  },
  "include": ["./shims.d.ts", "src/**/*"],
  "exclude": ["node_modules", "dist"]
}

shims.d.ts

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module '*.md' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/src/assets/icon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

scripts/vitePluginSyncmd.ts

import { Plugin } from "vite";
import chokidar from "chokidar";
import path from "path";
import fs from "fs-extra";

function docFileName(path: string) {
  const ret = path.split("/__docs__/");
  if (ret.length === 2) {
    return ret;
  }
  return [null, null];
}

function syncdocServer({ root }) {
  const componentsDir = path.join(root, "../elenext/src/components");
  const docsPath = (file) => path.join(root, "src/__docs__", file);
  const watcher = chokidar.watch(`${componentsDir}/**/__docs__/*.md`);
  watcher
    .on("add", async (path) => {
      const [, file] = docFileName(path);
      if (file) {
        try {
          await fs.copy(path, docsPath(file));
        } catch (err) {
          console.error(err);
        }
      }
    })
    .on("change", async (path) => {
      const [, file] = docFileName(path);
      if (file) {
        try {
          await fs.copy(path, docsPath(file));
        } catch (err) {
          console.error(err);
        }
      }
    })
    .on("unlink", async (path) => {
      const [, file] = docFileName(path);
      if (file) {
        try {
          await fs.remove(docsPath(file));
        } catch (err) {
          console.error(err);
        }
      }
    });
}

function vitePluginSyncmd(): Plugin {
  return {
    name: "Syncmd",
    configureServer(server) {
      syncdocServer({ root: server.config.root });
    },
  };
}

export default vitePluginSyncmd;

src/__docs__/Button.zh-CN.md

---
title: Button
wrapperClass: md-button
---

# Button 按钮

常用的操作按钮

## 按钮颜色

使用`color`属性来定义 Button 的颜色

`color`: 'primary' | 'success' | 'info' | 'warning' | 'danger'

## Button Props

| 参数        | 说明           | 类型                                                             | 默认值 |
| ----------- | -------------- | ---------------------------------------------------------------- | ------ |
| color       | 类型           | 'primary' / 'success' / 'warning' / 'danger' / 'info' / 'string' | -      |

components/AppLayout.vue

<template>
  <div class="demo-layout">
    <div>
      <div class="demo-header">
        <div class="layout-center">
          <div align="middle">
            <div :flex="1">
              <Logo />
            </div>
            <div>
              <div mode="horizontal">
                <div>
                  <input v-model="data.primaryColor" />
                </div>
                <div>
                  <a href="https://github.com/JasKang/elenext" target="__blank">GitHub</a>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div>
        <div class="layout-center">
          <div align="top" :wrap="false">
            <div :flex="'200px'">
              <div style="padding-top: 40px">
                <div mode="vertical" :current-path="route.path">
                  <template v-for="menu in menus" :key="menu.title">
                    <div :title="menu.title">
                      <div v-for="item in menu.items" :key="item" :path="`/${item.name.toLowerCase()}`">
                        {{ `${item.name}-Aaron` }}
                      </div>
                    </div>
                  </template>
                </div>
              </div>
            </div>
            <div :flex="1">
              <div class="site-content">
                <router-view />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue'
import { useRoute } from 'vue-router'
import menus from '../menus'

export default defineComponent({
  name: 'AppLayout',
  setup() {
    const route = useRoute()


    const data = reactive({
      primaryColor: '#409eff',
    })

    return {
      route,
      menus,
      data,
    }
  },
})
</script>
<style lang="scss">
.demo-layout {
  height: 100vh;
}
.layout-center {
  max-width: 1200px;
  width: 100vw;
  margin: 0 auto;
}
.site-content {
  width: 100%;
  padding: 20px;
  // max-width: 900px;
  margin: 0 auto;
}

.demo-aside {
  border-right: solid 1px #e6e6e6;
}
.demo-header {
  border-bottom: solid 1px #e6e6e6;
  // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
</style>

App.vue

<template>
  <div class="box">111</div>
  <router-view />
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'App',
  components: {}
})
</script>

main.ts

import 'vite-plugin-vuedoc/style.css';
import { createApp } from 'vue';

import AzUI from 'azui';
import 'azui/dist/lib/index.css';

import { router } from './router';
import App from './App.vue';

const app = createApp(App);

app.use(AzUI);
app.use(router);
app.mount('#app');

menus.ts

import { Component, defineAsyncComponent } from 'vue'
import Button from './__docs__/Button.zh-CN.md'

type MenuItemType = {
  name: string
  component: (() => Promise<Component>) | Component
}
type MenuType = {
  title: string
  items: MenuItemType[]
}

export default [
  {
    title: 'Basic',
    items: [
      { name: 'Button', component: Button },
    ]
  }
] as MenuType[]

router.ts

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import AppLayout from './components/AppLayout.vue'
import menus from './menus'

export const router = createRouter({
  history: createWebHistory(),
  strict: true,
  routes: [
    { path: '/'},
    {
      path: '/component',
      name: 'Layout',
      component: AppLayout,
      redirect: '/button',
      children: menus.reduce((prev, item) => {
        const _routes = item.items.map(i => {
          console.log(i.component)
          return {
            path: `/${i.name.toLowerCase()}`,
            name: i.name,
            component: i.component,
          }
        })
        prev.push(..._routes)
        return prev
      }, [] as RouteRecordRaw[]),
    },
  ],
})

文档部分完成,接下来处理组件开发部分,其实组件开发部分可以和文档部分放在一起,使用不同的路由,但是两者的功能不相同,所以单独配置了一个子项目。

开发部分

执行如下命令:

cd ../..
cd .\packages\play\

目录结构:

├─public
├─scripts
├─index.html

└─src
    ├─App.vue
    └─main.ts

package.json,相关依赖自行安装

{
  "name": "@azui/play",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "tsrv test"
  },
  "dependencies": {
    "azui": "0.0.1",
    "vue": "^3.0.7"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.1.5",
    "node-sass": "^5.0.0",
    "sass": "^1.32.11",
    "sass-loader": "^11.0.1",
    "vite": "^2.0.5"
  }
}

shims.d.ts

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module '*.md' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "lib": ["esnext", "dom"],
    "types": ["vite/client"],
    "baseUrl": "."
  },
  "include": ["./shims.d.ts", "src/**/*"],
  "exclude": ["node_modules", "dist"]
}

vite.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";


export default defineConfig({
  server: {
    port: 8080,
  },
  optimizeDeps: {
    exclude: ["azui"],
  },
  plugins: [
    vue()
  ]
});

这样的话组件开发部分也就配置好了,现在就是启动项目的时候,返回到根目录,在根目录的package.json中添加scripts:

项目启动

root package.json

{
  "scripts": {
    "dev": "yarn workspace azui run dev",
    "start": "yarn workspace @azui/docs run dev",
    "play": "yarn workspace @azui/play run dev",
    "test": "yarn workspace azui run test",
    "lint": "eslint --no-error-on-unmatched-pattern --ext .vue --ext .ts --ext .tsx packages/**/ --fix"
  }
}

需要注意的是,一定要先运行yarn dev根据dev来运行打包出开发环境所需要的组件,之后再运行start或者play,记的有两个命令行窗口运行哦,如果直接运行startplay会抛出错误:

[plugin:vite:import-analysis] 
Failed to resolve entry for package "azui". 
The package may have incorrect main/module/exports specified in its package.json.

这是因为在项目中无法找到所依赖的组件库~这一点需要特殊注意。

结束语

要搭建一个相对完善的组件库,都是需要经过一系列项目的沉淀的。目前而言,组件库的开发流程上依然会存在一些问题,比如版本管理、升级回退等。时间是最好的老师,相信在后面的迭代中,我们依然能够保持初心与热情,积极探索与发现,构建出更加完善的前端工程体系。


Aaron
4k 声望6.1k 粉丝

Easy life, happy elimination of bugs.