動機
當市面上主流的組件庫不能滿足我們業務需求的時候,那麼我們就有必要開發一套屬於自己團隊的組件庫。
環境
開發環境:
- vue 3.0
- vue/cli 4.5.13
- nodeJs 12.16.3
- npm 6.14.4
步驟
創建項目
使用 vue-cli 創建一個 vue3 項目,假設項目名為 custom-npm-ui
$ vue create custom-npm-ui
手動選擇設置。
規劃目錄
├─ build // 打包腳本,用於存放打包配置文件
│ ├─ rollup.config.js
├─ examples // 原 src 目錄,改成 examples 用於示例展示
│ ├─ App.vue
│ ├─ main.ts
├─ packages // 新增 packages 目錄,用於編寫存放組件,如button
│ ├─ SubmitForm
│ │ ├─ src/
│ │ ├─ index.ts
│ ├─ index.ts
├─ typings // 新增 typings 目錄, 用於存放 .d.ts 文件,把 shims-vue.d.ts 移動到這裏
│ ├─ shims-vue.d.ts
├─ .npmignore // 新增 .npmignore 配置文件
├─ vue.config.js // 新增 vue.config.js 配置文件
將 src 目錄改為 examples ,並將裏面的 assets 和 components 目錄刪除,移除 App.vue 裏的組件引用。
項目配置
vue.config.js
新增 vue.config.js 配置文件,適配重新規劃後的項目目錄:
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path')
module.exports = {
// 修改 pages 入口
pages: {
index: {
entry: "examples/main.ts", //入口
template: "public/index.html", //模板
filename: "index.html" //輸出文件
}
},
// 擴展 webpack 配置
chainWebpack: (config) => {
// 新增一個 ~ 指向 packages 目錄, 方便示例代碼中使用
config.resolve.alias
.set('~', path.resolve('packages'))
}
}
.npmignore
新增 .npmignore 配置文件,組件發佈到 npm中,只有編譯後的發佈目錄(例如lib)、package.json、README.md才是需要被髮布的,所以我們需要設置忽略目錄和文件
# 忽略目錄
.idea
.vscode
build/
docs/
examples/
packages/
public/
node_modules/
typings/
# 忽略指定文件
babel.config.js
tsconfig.json
tslint.json
vue.config.js
.gitignore
.browserslistrc
*.map
或者配置pkg#files:
"files": [
"lib/",
"package.json",
"README.md"
],
安裝依賴後的目錄結構:
└─custom-npm-ui
│ package.json
│ README.md
└─lib
index.css
index.esm.js
index.min.js
tsconfig.json
修改 tsconfig.json 中 paths 的路徑
"paths": {
"@/*": [
"src/*"
]
}
改為
"paths": {
"~/*": [
"packages/*"
]
}
Notes:typescript支持的別名。
修改 include 的路徑
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
]
改為
"include": [
"examples/**/*.ts",
"examples/**/*.tsx",
"examples/**/*.vue",
"packages/**/*.ts",
"packages/**/*.tsx",
"packages/**/*.vue",
"typings/**/*.ts",
"typings/shims-vue.d.ts",
"tests/**/*.ts",
"tests/**/*.tsx"
]
package.json
修改 package.json 中發佈到 npm 的字段
name:包名,該名字是唯一的。可在npm遠程源搜索名字,如果存在則需換個名字。version:版本號,每次發佈至 npm 需要修改版本號,不能和歷史版本號相同。description:描述。main:入口文件,該字段需指向我們最終編譯後的包文件。typings:types文件,TS組件需要。keyword:關鍵字,以空格分離希望用户最終搜索的詞。author:作者信息private:是否私有,需要修改為 false 才能發佈到 npmlicense: 開源協議
參考設置:
{
"name": "custom-npm-ui",
"version": "0.1.0",
"private": false,
"description": "基於ElementPlus二次開發的前端組件庫",
"main": "lib/index.min.js",
"module": "lib/index.esm.js",
"typings": "lib/index.d.ts",
"keyword": "vue3 element-plus",
"license": "MIT",
"author": {
"name": "yourname",
"email": "youremail@163.com"
}
}
在 package.json 的 scripts 新增編譯和發佈的命令
"scripts": {
"build": "yarn build:clean && yarn build:lib && yarn build:esm-bundle && rimraf lib/demo.html",
"build:clean": "rimraf lib",
"build:lib": "vue-cli-service build --target lib --name index --dest lib packages/index.ts",
"build:esm-bundle": "rollup --config ./build/rollup.config.js"
}
其中 build:lib 是利用 vue-cli 進行 umd 方式打包,build:esm-bundle 是利用 rollup 進行 es 方式打包。
build:lib具體參數解析如下:
--target: 構建目標,默認為應用模式。改為lib啓用庫模式。--name: 輸出文件名--dest: 輸出目錄,默認dist。改成lib[entry]: 入口文件路徑,默認為src/App.vue。這裏我們指定編譯packages/組件庫目錄。
build:esm-bundle打包後的資源在webpack2+、rollup環境中可通過pkg#module配置載入使用。
rollup.config.js
新增 build/rollup.config.js,rollup 打包腳本:
import cjs from "@rollup/plugin-commonjs"; // commonjs轉es module —— rollup只支持es module
import resolve from "@rollup/plugin-node-resolve"; // 搭配@rollup/plugin-commonjs使用
// import ts from '@rollup/plugin-typescript' // 【報錯】使用ts報錯
import typescript from "rollup-plugin-typescript2"; // 解析TS語法
import vue from "rollup-plugin-vue"; // 解析vue
import babel from "@rollup/plugin-babel";
import scss from "rollup-plugin-scss"; // 解析scss
// import requireContext from "rollup-plugin-require-context"; // 【不可用】支持webpack的require.context API —— 需要安裝npm install --save-dev generate-source-map@0.0.5
import { writeFileSync, existsSync, mkdirSync } from "fs";
const extensions = [".js", ".ts", ".vue"];
export default {
input: "packages/index.ts",
output: [
{
file: "lib/index.esm.js", // 多文件輸出的話,需要使用dir替代file
format: "es",
globals: {
vue: "Vue", // 告訴rollup全局變量Vue即是vue
},
},
],
extensions,
plugins: [ // 順序很重要
scss({
output: function (styles, styleNodes) {
if (!existsSync("lib/")) {
mkdirSync("lib/");
}
writeFileSync("lib/index.css", styles);
},
}),
vue({
compileTemplate: true,
}),
// requireContext(),
resolve({
jsnext: true,
main: true,
browser: true,
extensions,
}),
cjs(),
typescript(),
babel({}),
],
external: ["vue", "element-plus"],
};
開發組件
注意事項
- 組件內不能使用懶加載
- 組件不能使用
require.context()統一管理 - 不支持
JSX語法編寫模版 —— 更好的選擇React
依賴安裝
環境依賴
$ npm i -D rimraf rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-typescript2 rollup-plugin-vue @rollup/plugin-babel rollup-plugin-scss
開發依賴
$ npm i -D element-plus@1.0.2-beta.69 babel-plugin-import
配置.babel.config.js
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[
"import",
{
libraryName: "element-plus",
},
],
],
};
更新examples/main.ts:
import { createApp } from "vue";
import App from "./App.vue";
import "element-plus/lib/theme-chalk/index.css";
createApp(App).mount("#app");
編寫組件
在 packages 目錄下新建 index.ts 文件和 SubmitForm/ 文件夾,在 SubmitForm 下新建 index.ts和 src/index.vue,結構如下:
.
├── SubmitForm
│ ├── SubmitForm.stories.ts
│ ├── index.ts
│ └── src
│ ├── FormRender.vue
│ ├── fileds
│ │ ├── Color.vue
│ │ ├── Datetime.vue
│ │ ├── Radio.vue
│ │ ├── Select.vue
│ │ ├── Switch.vue
│ │ ├── Text.vue
│ │ ├── Upload.vue
│ │ ├── hooks
│ │ │ └── useValueHandleHook.ts
│ │ └── index.ts
│ ├── index.vue
│ ├── schemas
│ │ ├── baseSchema.ts
│ │ └── schemasProp.ts
│ └── store
│ └── index.ts
├── common
│ └── getType.ts
└── index.ts
packages/SubmitForm/src/index.vue
<script lang="ts">
import { computed, defineComponent } from "vue";
import { ElForm } from "element-plus";
import { FormPropsType } from "./schemas/schemasProp";
import FormRender from "./FormRender.vue";
import { values, updateValues } from "./store";
export default defineComponent({
name: "SubmitForm",
props: FormPropsType,
emits: ["runtimeChange"],
components: {
"el-form": ElForm,
FormRender,
},
setup(props, { emit }) {
const schemasCpt = computed(() => props.schema);
const defaultValueCpt = computed(() => props.defaultValue);
function handleRuntimeChange(name: string, value: any) {
updateValues(name, value);
emit("runtimeChange", name, value);
}
return {
schemasCpt,
defaultValueCpt,
values,
handleRuntimeChange,
};
},
});
</script>
<template>
<el-form label-width="100px" :model="values">
<template v-for="(schema, idx) of schemasCpt" :key="idx">
<FormRender
:schema="schema"
:defaultValue="defaultValueCpt"
v-bind="$attrs"
:onRuntimeChange="handleRuntimeChange"
/>
</template>
</el-form>
</template>
packages/SubmitForm/index.ts,單獨組件的入口文件,在其他項目可以使用 import { SubmitForm } from 'custom-npm-ui' 方式進行單個組件引用
import type { App } from "vue";
import SubmitForm from "./src/index.vue";
// 定義 install 方法,接收 Vue 作為參數。如果使用 use 註冊插件,那麼所有的組件都會被註冊
SubmitForm.install = function (Vue: App) {
// 遍歷註冊全局組件
Vue.component(SubmitForm.name, SubmitForm);
};
export default SubmitForm;
packages/index.ts 作為組件庫的入口文件,可以在其他項目的 main.ts 引入整個組件庫,內容如下
import type { App } from "vue";
import SubmitForm from "./SubmitForm";
const components = [SubmitForm];
// 定義 install 方法,接收 Vue 作為參數。如果使用 use 註冊插件,那麼所有的組件都會被註冊
const install = function (Vue: App): void {
// 遍歷註冊全局組件
components.map((component) => Vue.component(component.name, component));
};
export {
// 以下是具體的組件列表
SubmitForm,
};
export default {
// 導出的對象必須具有 install,才能被 Vue.use() 方法安裝
install,
};
這樣,我們就完成一個簡單的 SubmitForm 組件,後續需要擴展其他組件,按照 SubmitForm 的結構進行開發,並且在 index.ts 文件中 components 組件列表添加即可。
編寫示例調試
examples/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import CustomeUI from "~/index";
import "element-plus/lib/theme-chalk/index.css";
createApp(App).use(CustomeUI).mount("#app");
examples/App.vue 刪除項目初始化的 HelloWorld 組件
<script lang="ts">
import { defineComponent, reactive, ref, toRaw } from "vue";
import formSchema from "./layouts/case.layout";
export default defineComponent({
name: "App",
components: {},
setup() {
const submitFormRef = ref();
const schema = reactive(formSchema);
const defaultValues = reactive({});
function formRuntimeChange(name: string, value: any) {
console.log(name, " = ", value);
}
function submit() {
console.log(submitFormRef.value.values);
}
return {
submitFormRef,
schema,
defaultValues,
formRuntimeChange,
submit,
};
},
});
</script>
<template>
<submit-form
ref="submitFormRef"
:schema="schema"
:defaultValue="defaultValues"
@runtimeChange="formRuntimeChange"
>
</submit-form>
<button @click="submit">保存</button>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
啓動項目,測試一下
$ npm run serve
組件開發完成後,執行編譯庫命令:
$ npm run build
引入打包後的文件迴歸測試一下,沒有問題再發布到 npm 倉庫。
在示例入口 main.ts 引用我們的組件庫:
import { createApp } from "vue";
import App from "./App.vue";
import CustomeUI from "../lib/index.esm.js";
import "element-plus/lib/theme-chalk/index.css";
createApp(App).use(CustomeUI).mount("#app");
編寫聲明文件
創建目錄結構
.
├── typings
│ ├── index.d.ts
│ ├── component.d.ts
│ └── packages
│ └── submit-form.vue.d.ts
├── common
│ └── getType.ts
└── index.ts
更新package.json配置
// package.json
{
...
"typings": "./typings/index.d.ts",
"files": [
"lib/",
"package.json",
"typings/"
],
"publishConfig": {
"registry": "https://abc.com/"
}
}
核心文件
// typings/index.d.ts
import type { App } from "vue";
export * from "./component.d";
export declare const install: (app: App, opt: any) => void;
declare const _default: {
install: (app: App<any>, opt: any) => void;
};
export default _default;
// typings/component.d.ts
export { default as SubmitForm } from "./packages/submit-form.d";
// typings/packages/submit-form.d.ts
import {
DefineComponent,
ComponentOptionsMixin,
VNodeProps,
AllowedComponentProps,
ComponentCustomProps,
EmitsOptions,
ComputedGetter,
WritableComputedOptions,
} from "vue";
import { FormRowType } from "../schemas/schemasProp";
declare const _default: DefineComponent<
Record<string, unknown>,
{
refName: string;
schema: FormRowType[];
defaultValue: Record<string, any>;
},
Record<string, unknown>,
Record<string, ComputedGetter<any> | WritableComputedOptions<any>>, // computed
Record<string, () => void>, // methods
ComponentOptionsMixin,
ComponentOptionsMixin,
EmitsOptions,
string,
VNodeProps & AllowedComponentProps & ComponentCustomProps,
Readonly<Record<string, unknown> & Record<string, unknown>>,
Record<string, unknown>
>;
export default _default;
發佈組件
配置NPM倉庫地址
組件開發並測試通過後,就可以發佈到 npm 倉庫提供給其他項目使用了,首先編寫.npmrc文件配置要上傳的源地址:
registry=https://abc.com
更推薦更新pkg#publishConfig指定倉庫地址:
"publishConfig": {
"registry": "https://abc.com"
},
Notes:使用.npmrc配置NPM倉庫地址,nrm無法切換源。
獲取NPM賬號、密碼
在npm官網註冊即可
登錄npm賬號
在項目中 terminal 命令窗口登錄 npm 賬號
$ npm login
Username:
Password:
Email:(this IS public)
輸入在 npm的賬號、密碼、郵箱
發佈
$ npm publish
組件文檔
創建Storybook友好型環境
在項目中 terminal 命令窗口執行命令:
$ npx -p @storybook/cli sb init
storybook是一個可以輔助UI開發的工具,是一個UI組件的開發環境。
在sb init初始化過程中,storybook會檢查現有項目的dependencies,然後依據項目的現有框架,提供最佳的組裝方式。
Storybook初始化做了以下步驟:
- 安裝
Storybook需要的依賴 - 更新
pkg#run-script -
增加預設的配置文件
.storybook/main.js.storybook/preview.js
- 增加示例模版
stories/
更新package.json
...
"scripts": {
"storybook": "start-storybook -p 6006 -h 0.0.0.0", // 啓動本地服務以預覽
"build-storybook": "build-storybook" // 構建
},
...
$ npm run storybook # 啓動本地服務訪問storybook項目
更新目錄結構
├─ .storybook // 預設的配置文件
│ ├─ main.js // 入口文件
│ ├─ preview.js // 控制Stories的呈現、全局資源的加載
├─ stories // 示例模版
main.js
module.exports = {
"stories": [ // Storybook會抓取、載入配置路徑下的指定文件渲染展示
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [ // Storybook所用插件 —— Storybook功能增強
"@storybook/addon-links",
"@storybook/addon-essentials"
],
"framework": "@storybook/vue3" // Storybook所用框架 —— Vue環境支持
}
編寫示例
入口配置
更新.storybook/main.js
module.exports = {
"stories": [ // Storybook會抓取、載入配置路徑下的指定文件渲染展示
"../packages/**/*.stories.@(js|jsx|ts|tsx)",
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
...
}
組件Story編寫
import SubmitForm from "./index"; // 引入組件
import { SchemaType, RuleTrigger } from "./src/schemas/baseSchema";
const caseSchema = [ // 示例數據
{
key: "moduleName",
name: "title",
type: SchemaType.Text,
label: "欄目名稱",
placeholder: "請輸入欄目名稱",
attrs: {
//
},
rules: [
{
required: true,
message: "欄目名稱必填~",
trigger: RuleTrigger.Blur,
},
],
},
...
];
export default {
title: "ui組件/SubmitForm", // 展示標題:使用路徑定義命名空間 —— 分組、分類
component: SubmitForm,
};
const Template = (args: any) => ({ // 渲染組件
components: { SubmitForm },
setup() {
return {
...args,
};
},
template: '<submit-form :schema="schema"></submit-form>',
});
export const 基本應用 = Template.bind({}); // 組件應用示例
(基本應用 as any).args = {
schema: caseSchema,
ref: "submitFormRef",
};
可以使用props&computed去承接args這樣更符合Vue3的書寫格式:
// 後續的補充內容,和此處上下文無關。
const Template = (args: any) => ({
props: Object.keys(args),
components: { SubmitForm, ElButton },
setup(props) {
const refName = computed(() => props.refName)
const submitFormRef = ref();
function submit() {
console.log(submitFormRef.value.values);
}
function onRuntimeChange(name: string, value: any) {
console.log(name, " = ", value);
}
return {
submit,
onRuntimeChange,
[refName.value]: submitFormRef,
...props,
};
},
template: `
<submit-form :schema="schema" :ref="refName" @runtimeChange="onRuntimeChange"></submit-form>
<el-button @click="submit">提交</el-button>
`,
});
全局依賴配置
因為示例代碼中依賴element-plus,通過上述展現的頁面沒有樣式,所以,StoryBook渲染需要額外引入element-plus主題:
// preview.js
import "element-plus/lib/theme-chalk/index.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
}
}
啓動本地服務
更新命令腳本
// package.json
"scripts": {
"storybook": "start-storybook -p 6006 -h 0.0.0.0",
"build-storybook": "build-storybook"
},
-h 0.0.0.0以支持局域網訪問。
執行命令
$ npm run storybook
效果展示
在Stories中使用第三方UI庫
以ElementPlus為例:
全局配置
如果babel.config沒有配置按需加載,可直接編輯.storybook/preview.js:
// .storybook/preview.js
import elementPlus from 'element-plus';
import { app } from '@storybook/vue3'
app.use(elementPlus);
export const decorators = [
(story) => ({
components: { story, elementPlus },
template: '<elementPlus><story/></elementPlus>'
})
];
import "element-plus/lib/theme-chalk/index.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
}
}
Notes:配置按需加載後,import elementPlus from 'element-plus';導入elementPlus報錯:elementPlus is not defined —— 全局加載、按需加載不能在同一項目中使用。
按需加載
在需要使用ElementPlus的Stories中直接引入即可:
// packages/SubmitForm/SubmitForm.stories.ts
import { ElButton } from 'element-plus';
import SubmitForm from "./index";
import { SchemaType, RuleTrigger } from "./src/schemas/baseSchema";
const caseSchema = [
{
key: "moduleName",
name: "title",
type: SchemaType.Text,
label: "欄目名稱",
placeholder: "請輸入欄目名稱",
attrs: {
//
},
rules: [
{
required: true,
message: "欄目名稱必填~",
trigger: RuleTrigger.Blur,
},
],
},
...
];
export default {
title: "ui組件/SubmitForm",
component: SubmitForm,
};
const Template = (args: any) => ({
components: { SubmitForm, ElButton },
setup() {
return {
...args,
};
},
template: '<submit-form :schema="schema" ref="submitFormRef"></submit-form><el-button @click="submit">提交</el-button>',
});
export const 基本應用 = Template.bind({});
(基本應用 as any).args = {
schema: caseSchema,
};
示例代碼添加交互
// packages/SubmitForm/SubmitForm.stories.ts
import { ElButton } from "element-plus";
import { ref } from "vue";
import SubmitForm from "./index";
import { SchemaType, RuleTrigger } from "./src/schemas/baseSchema";
const caseSchema = [
{
key: "moduleName",
name: "title",
type: SchemaType.Text,
label: "欄目名稱",
placeholder: "請輸入欄目名稱",
attrs: {
//
},
rules: [
{
required: true,
message: "欄目名稱必填~",
trigger: RuleTrigger.Blur,
},
],
},
...
];
export default {
title: "ui組件/SubmitForm",
component: SubmitForm,
};
const Template = (args: any) => ({
components: { SubmitForm, ElButton },
setup() {
const { refName } = args;
const submitFormRef = ref();
function submit() {
console.log(submitFormRef.value.values);
}
function onRuntimeChange(name: string, value: any) {
console.log(name, " = ", value);
}
return {
submit,
onRuntimeChange,
[refName]: submitFormRef,
...args,
};
},
template: `
<submit-form :schema="schema" :ref="refName" @runtimeChange="onRuntimeChange"></submit-form>
<el-button @click="submit">提交</el-button>
`,
});
export const 基本應用 = Template.bind({});
(基本應用 as any).args = {
refName: "submitFormRef",
schema: caseSchema,
};
這裏做了兩件事:
- 增加提交按鈕
- 增加數據提交交互
配置參數詳情
默認文檔展示
默認查看到的文檔參數是以下樣子:
參數配置
通過配置argTypes可以補充參數信息:
// packages/SubmitForm/SubmitForm.stories.ts
...
export default {
title: "ui組件/SubmitForm",
component: SubmitForm,
argTypes: {
refName: {
description: '表單組件引用',
type: {
required: true,
},
table: {
defaultValue: {
summary: 'defaultNameRef',
}
},
control: {
type: 'text'
}
},
schema: {
type: {
required: true,
},
table: {
type: {
summary: '渲染表單所需JSON結構',
detail: 'JSON結構包含表單渲染、交互所需要的必要字段,也包含表單的校驗規則',
},
defaultValue: {
summary: '[]',
detail: `[
{
key: "moduleName",
name: "title",
type: SchemaType.Text,
label: "欄目名稱",
placeholder: "請輸入欄目名稱",
attrs: {
//
},
rules: [
{
required: true,
message: "欄目名稱必填~",
trigger: RuleTrigger.Blur,
},
],
}
]
`
}
}
},
runtimeChange: {
description: '實時監聽表單的更新',
table: {
category: 'Events',
},
}
}
};
...
詳細配置見鏈接。
理想效果
文檔部署
執行命令:
$ npm run build-storybook
生成靜態頁面,直接部署靜態頁面即可。
目錄結構:
│ 0.0a0da810.iframe.bundle.js
│ 0.0a0da810.iframe.bundle.js.LICENSE.txt
│ 0.0a0da810.iframe.bundle.js.map
│ 0.799c368cbe88266827ba.manager.bundle.js
│ 1.9ebd2fb519f6726108de.manager.bundle.js
│ 1.9face5ef.iframe.bundle.js
│ 1.9face5ef.iframe.bundle.js.LICENSE.txt
│ 1.9face5ef.iframe.bundle.js.map
│ 10.07ff4e93.iframe.bundle.js
│ 10.a85ea1a67689be8e19ff.manager.bundle.js
│ 11.f4e922583ae35da460f3.manager.bundle.js
│ 11.f4e922583ae35da460f3.manager.bundle.js.LICENSE.txt
│ 12.1415460941f0bdcb8fa8.manager.bundle.js
│ 2.8a28fd4e.iframe.bundle.js
│ 2.8a28fd4e.iframe.bundle.js.LICENSE.txt
│ 2.8a28fd4e.iframe.bundle.js.map
│ 3.50826d47.iframe.bundle.js
│ 4.779a6efa.iframe.bundle.js
│ 5.f459d151315e6780c20f.manager.bundle.js
│ 5.f459d151315e6780c20f.manager.bundle.js.LICENSE.txt
│ 6.3bd64d820f3745f262ff.manager.bundle.js
│ 7.3d04765dbf3f1dcd706c.manager.bundle.js
│ 8.b541eadfcb9164835dfc.manager.bundle.js
│ 8.c6cb825f.iframe.bundle.js
│ 9.411ac8e451bbb10926c7.manager.bundle.js
│ 9.51f84f13.iframe.bundle.js
│ 9.51f84f13.iframe.bundle.js.LICENSE.txt
│ 9.51f84f13.iframe.bundle.js.map
│ favicon.ico
│ iframe.html
│ index.html // 入口頁面
│ main.4c3140a78c06c6b39fba.manager.bundle.js
│ main.e86e1837.iframe.bundle.js
│ runtime~main.1e621db5.iframe.bundle.js
│ runtime~main.91a0c7330ab317d35c4a.manager.bundle.js
│ vendors~main.0d1916dd840230bedd21.manager.bundle.js
│ vendors~main.0d1916dd840230bedd21.manager.bundle.js.LICENSE.txt
│ vendors~main.8b18b60f.iframe.bundle.js
│ vendors~main.8b18b60f.iframe.bundle.js.LICENSE.txt
│ vendors~main.8b18b60f.iframe.bundle.js.map
│
└─static
└─media
element-icons.5bba4d97.ttf
element-icons.dcdb1ef8.woff
參考文檔
- Storybook官網
參考文章很多,如懷疑內容參考,請聯繫,會考慮增加到參考文檔中