We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
众所周知,基础组件库是从业务中抽象出来的,功能相对单一、独立,在整个系统的代码层次中位于最底层,被其他代码所依赖。由于考虑到扩展性及通用性,基础组件基本不包含任何业务代码或 http 请求。但实际开发中又会存在着对某块业务的封装,或者是交互设计师针对当前业务做出特定交互设计,这时就需要一个系统来承载这些组件,便于跨业务单元进行调用。
从下列几点来简述组件库的搭建过程:
vue-cli3 相较于2的版本比较大的更新是避免用户直接操作 webpack 配置,默认场景下底层配置项在项目中不可见(可手动导出)。脚手架实现了对 webpack 配置的二次封装,形成了一套新的配置语法,在 vue.config.js 进行配置。官方推荐使用链式操作(基于 webpack-chain),如下:
// vue.config.js module.exports = { chainWebpack: config => { config.module .rule('vue') .use('vue-loader') .loader('vue-loader') .tap(options => { // 修改它的选项... return options }) } }
组件库前期使用 vue-cli3,后期抛弃了,原因如下:
参考: https://cli.vuejs.org/zh/guide/webpack.html
核心 npm 包:
typescript
ts-loader
vue-class-component
vue-property-decorator
@babel/plugin-proposal-class-properties
@babel/plugin-proposal-decorators
配置流程
tsconfig.json 关键配置
{ // 编译配置 "compilerOptions": { // 编译输出目标 ES 版本 "target": "ES5", // 采用的模块系统 "module": "ESNext", // 启用装饰器 "experimentalDecorators": true, // 允许编译javascript文件 "allowJs": true, }, // 编译的文件夹 "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue" ], // 排除文件夹 "exclude": [ "node_modules" ] }
babel.config.js 关键配置
module.exports = function (api) { api && api.cache(false) return { presets: [ // 预设,等于配置一系列支持 ts 的 babel 插件 "@babel/preset-typescript" ], plugins: [ // 以下插件具有先后顺序 // 首先编译装饰器语法 [ "@babel/plugin-proposal-decorators", { "legacy": true } ], // 再编译 class 语法 "@babel/plugin-proposal-class-properties" ] } }
Vue 组件示例(js 部分)
<script lang="ts"> import scout from "wy-ssr-scout" import { Component, Prop, Vue, Provide, PropSync, Emit } from "vue-property-decorator" import { WeTabBar, WeTabBarItem } from "wand-ui" @Component({ components: { WeTabBar, WeTabBarItem } }) export default class WeUserDemo extends Vue { tabSelected: number = 0 msg = "这是demo弹窗" @Prop({ type: String, default: "确认要取消吗?" }) readonly title!: string @Prop({ type: String, default: "" }) readonly content!: string @PropSync("showToast", { type: Boolean }) private show!: boolean @Emit("on-confirm") confirm(): string { scout.click("测试埋点", { page: "demo", type: 2 }) this.show = false return "参数被传递出来啦" } } </script>
参考:
https://www.tslang.cn/docs/handbook/tsconfig-json.html https://github.com/TypeStrong/ts-loader https://github.com/vuejs/vue-class-component https://github.com/kaorun343/vue-property-decorator https://babeljs.io/docs/en/next/babel-preset-typescript https://babeljs.io/docs/en/babel-plugin-proposal-class-properties https://babeljs.io/docs/en/babel-plugin-proposal-decorators
配置方案(下文提到的配置方案全部基于 webpack):
const webpack = require('webpack') module.exports = { devServer: { // 启用 webpack 的模块热替换 hot: true, // 只允许热替换 hotOnly: true, // 单页应用刷新 404 historyApiFallback: true, // 去除 host 检测 disableHostCheck: true }, // 模块热替换插件 new webpack.HotModuleReplacementPlugin() }
以上是通用热替换方案,但 ts 项目没有那么简单,通过以上配置仅实现了热更新,热替换还需要加入以下配置
module.exports = { module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: [ 'babel-loader', { loader: 'ts-loader', options: { transpileOnly: true, // 关键代码段 // 当遇见 .vue 结尾的文件默认添加 .ts/.tsx // ts-loader 内部实现了 ts 代码段的热替换 appendTsSuffixTo: [/\.vue$/], appendTsxSuffixTo: [/\.vue$/] } } ] } ] } }
https://webpack.docschina.org/plugins/hot-module-replacement-plugin/ https://webpack.docschina.org/configuration/dev-server/ https://github.com/microsoft/TypeScript-Vue-Starter/blob/master/webpack.config.js
一个业务组件被设计出来必然要支持各种机型,组件如何去更好地适配,即要在开发时不给开发人员带来困扰,又能轻易地接入到各个系统当中去,这是组件库在搭建时必然要考虑的问题。常见的适配方案:
百分比 + px
rem
vw
最终采用 vw 的方案,理由如下:
如何配置:
postcss
postcss-loader
postcss-px-to-viewport
postcss-viewport-units
viewport-units-buggyfill
webpack.common.js 关键代码段
module.exports = { module: { rules: [ { test: /\.less$/, sideEffects: true, use: [ 'vue-style-loader', 'css-loader', // 具有先后顺序,从上至下,从右往左 'postcss-loader', 'less-loader' ] }, { test: /\.css$/, use: [ 'vue-style-loader', 'style-loader', 'css-loader', 'postcss-loader' ] } ] } }
module.exports = { "plugins": { // 自动添加前缀插件 "autoprefixer": {}, "postcss-px-to-viewport": { // 视口宽度、高度 "viewportWidth": 375, "viewportHeight": 667, // 指定`px`转换为视窗单位值的小数位数 "unitPrecision": 3, // 所要转换的单位 "viewportUnit": "vw", // 添加不转换的白名单 "selectorBlackList": [ ".ignore", ".hairlines" ], // 小于或等于`1px`不转换为视窗单位 "minPixelValue": 1, // 允许在媒体查询中转换`px` "mediaQuery": false }, // 为 viewport-units-buggyfill 添加 content属性 "postcss-viewport-units":{ filterRule: rule => rule.nodes.findIndex(i => i.prop === 'content') === -1 } } }
// index.html <script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script> <script> // vw兼容性处理viewport-units-buggyfill window.onload = function () { window.viewportUnitsBuggyfill.init({ hacks: window.viewportUnitsBuggyfillHacks }); } </script>
https://blog.csdn.net/qq_21729177/article/details/79466951 https://github.com/evrone/postcss-px-to-viewport https://github.com/rodneyrehm/viewport-units-buggyfill https://github.com/springuper/postcss-viewport-units
印象中的按需加载
import { WeUserDialog } from '@weiyi/wand-ui-user'
通过 babel-plugin-import 转译后的按需加载
import WeUserDialog from '@weiyi/wand-ui-user/lib/components/dialog/index.js' import '@weiyi/wand-ui-user/lib/components/dialog/index.css'
按需加载打包方式调研:
以 SFC .vue 单文件方式编写组件,打包时利用 vue-template-compiler 手动将文件分离为template、js、css,利用 babel 系列工具将 template 与 js 输出为 js 文件,利用 less/sass、postcss 等工具输出 css 文件,如 wand-ui;
vue-template-compiler
模板和 js 部分使用 jsx/tsx 编写,css 样式分离,可以进行较小成本地手动打包,如 vant-ui
充分利用已有的 loader,将按需加载的组件认为是多个 entry,利用 webpack 进行多入口打包,如 element-ui、nut-ui
...
实践:
webpack.prod.demand.js 关键代码段
// 获取所有组件入口的绝对地址 let entry = getEntry() module.exports = { mode: 'production', entry, output: { // 输出的文件目录及文件名 path: getPath('../lib'), filename: 'components/[name]/index.js', // 以库的形式输出 library: '[name]', // 该库可在所有的模块定义下都可运行,如 CommonJS, AMD 等 libraryTarget: 'umd', // 会对 UMD 的构建过程中的 AMD 模块进行命名 umdNamedDefine: true, globalObject: 'this' }, // 防止将某些 import 的包打包到 bundle 中,而是在运行时再去从外部获取这些扩展依赖 externals: [ { vue: { root: 'Vue', commonjs: 'vue', commonjs2: 'vue', amd: 'vue' }, lodash: 'lodash', axios: 'axios', uuid: 'uuid', moment: 'moment', 'vue-property-decorator': 'vue-property-decorator', 'wy-ssr-scout': 'wy-ssr-scout', 'js-cookie': 'js-cookie', }, /^wand-ui\/.+$/ ], optimization: { // 是否压缩 minimize: true }, plugins: [ // ... // 从 .vue 文件中分离 css,并输出到文件 new MiniCssExtractPlugin({ filename: 'components/[name]/index.css' }) // ... ] }
https://webpack.docschina.org/configuration/externals/#externals https://github.com/youzan/vant https://github.com/ElemeFE/element
思考:能不能通过程序来一键完成上述操作?
npm run create
eslint + prettier 保存时自动格式化,美化代码格式
本地站点搭建,保证自己的代码在生产环境下正确执行
npm run build:site npm run start
const Koa = require('koa') const app = new Koa() const { historyApiFallback } = require('koa2-connect-history-api-fallback') const static = require('koa-static') const path = require('path') const port = 8083 app.use( // 单页应用刷新 404 historyApiFallback({ rewrites: [{ from: /^\/site\/.*$/, to: '../site/index.html' }] }) ) app.use(static(path.join(__dirname, '../site/'))) app.listen(port, () => { console.log(`Server listening on: http://localhost:${port}`) })
https://github.com/jprichardson/node-fs-extra https://github.com/prettier/eslint-plugin-prettier https://github.com/ishen7/koa2-connect-history-api-fallback
module.exports = { module: { rules: [ test: /\.md$/, use: [ { loader: "vue-loader" }, { loader: "vue-markdown-loader/lib/markdown-compiler", options: { raw: true, wrapper: 'article', preprocess: function (MarkdownIt, Source) { MarkdownIt.renderer.rules.table_open = function () { return '<div class="table-container"><table class="table">' } MarkdownIt.renderer.rules.table_close = function () { return '</table></div>' } // 给代码块添加复制样式 const fence = MarkdownIt.renderer.rules.fence MarkdownIt.renderer.rules.fence = function (...args) { return '<div class="markdown-code-block"><div class="markdown-code--btn-copy" @click="copyMdCode" data-clipboard-text="">复制</div>' + fence(...args) + '</div>' } return Source } } } ] } }
// main.ts Vue.use(base) // base.ts import Clipboard from "clipboard" export default { install: function(Vue: any) { Vue.prototype.copyMdCode = function(e: any) { let clipboard = new Clipboard(".markdown-code--btn-copy", { target: function() { return e.target.nextElementSibling } }) clipboard.on("success", () => { alert("复制成功") clipboard.destroy() }) clipboard.on("error", () => { alert("该浏览器不支持自动复制") clipboard.destroy() }) } } }
https://github.com/QingWei-Li/vue-markdown-loader https://github.com/zenorocha/clipboard.js https://github.com/ant-design/babel-plugin-import
编写 npm 包必然要测试包的发布、引用是否正常, verdaccio 是一个很不错的选择,一键搭建。期待达到的效果,安装各种包一定快,并且通过一定的配置,找不到的依赖包会去外网下载。
// 本地找不到的包就代理到淘宝源 uplinks: taobao: url: https://registry.npm.taobao.org/ packages: '@*/*': # scoped packages access: $all publish: $all unpublish: $all proxy: taobao '**': access: $all publish: $all proxy: taobao
单文件打包过大,如 location 组件打包后 js 大小为 96k,如何解决?
分析:一部分代码来源于 babel 编译时插入的垫片代码,一部分代码来源于 import 进来的包的代码。
解决:移除 babel 中的垫片代码,利用 ts 的代码编译将代码编译为 ES5;打包时剔除 npm 包的依赖代码,在项目中运行时再安装,最终压缩后 js 大小为 25k。
代码压缩后 Vue 组件找不到文件名,导致组件注册失效?
分析:官方说明当 Vue 组件不设置 name 时默认会取类名作为组件名,但测试的两种打包方式(全局打包、按需打包)均出现不指定 name 名抛错的问题。
解决:
@Component({ name: "WeUserAddress", components: { // ... } }) export default class WeUserAddress extends Vue { // ... }
commit 的时候进行 eslint 校验,因为本地安装了 nvm 进行 node 版本,导致提交时校验代码抛错,又考虑目前保存代码时已进行代码校验,放缓该方案的实践。
The text was updated successfully, but these errors were encountered:
好文帮顶
Sorry, something went wrong.
No branches or pull requests
基于 TypeScript + Webpack4 搭建 Vue 业务组件库(by 元丰)
背景
众所周知,基础组件库是从业务中抽象出来的,功能相对单一、独立,在整个系统的代码层次中位于最底层,被其他代码所依赖。由于考虑到扩展性及通用性,基础组件基本不包含任何业务代码或 http 请求。但实际开发中又会存在着对某块业务的封装,或者是交互设计师针对当前业务做出特定交互设计,这时就需要一个系统来承载这些组件,便于跨业务单元进行调用。
概述
从下列几点来简述组件库的搭建过程:
vue-cli3/webpack-chain
vue-cli3 相较于2的版本比较大的更新是避免用户直接操作 webpack 配置,默认场景下底层配置项在项目中不可见(可手动导出)。脚手架实现了对 webpack 配置的二次封装,形成了一套新的配置语法,在 vue.config.js 进行配置。官方推荐使用链式操作(基于 webpack-chain),如下:
组件库前期使用 vue-cli3,后期抛弃了,原因如下:
参考: https://cli.vuejs.org/zh/guide/webpack.html
TypeScript 支持
核心 npm 包:
typescript
ts-loader
vue-class-component
vue-property-decorator
@babel/plugin-proposal-class-properties
@babel/plugin-proposal-decorators
配置流程
tsconfig.json 关键配置
babel.config.js 关键配置
Vue 组件示例(js 部分)
参考:
https://www.tslang.cn/docs/handbook/tsconfig-json.html
https://github.com/TypeStrong/ts-loader
https://github.com/vuejs/vue-class-component
https://github.com/kaorun343/vue-property-decorator
https://babeljs.io/docs/en/next/babel-preset-typescript
https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
https://babeljs.io/docs/en/babel-plugin-proposal-decorators
热更新/模块热替换
配置方案(下文提到的配置方案全部基于 webpack):
以上是通用热替换方案,但 ts 项目没有那么简单,通过以上配置仅实现了热更新,热替换还需要加入以下配置
参考:
https://webpack.docschina.org/plugins/hot-module-replacement-plugin/
https://webpack.docschina.org/configuration/dev-server/
https://github.com/microsoft/TypeScript-Vue-Starter/blob/master/webpack.config.js
组件库的适配
一个业务组件被设计出来必然要支持各种机型,组件如何去更好地适配,即要在开发时不给开发人员带来困扰,又能轻易地接入到各个系统当中去,这是组件库在搭建时必然要考虑的问题。常见的适配方案:
百分比 + px
rem
vw
最终采用 vw 的方案,理由如下:
如何配置:
核心 npm 包:
postcss
postcss-loader
postcss-px-to-viewport
postcss-viewport-units
viewport-units-buggyfill
webpack.common.js 关键代码段
参考:
https://blog.csdn.net/qq_21729177/article/details/79466951
https://github.com/evrone/postcss-px-to-viewport
https://github.com/rodneyrehm/viewport-units-buggyfill
https://github.com/springuper/postcss-viewport-units
按需加载
印象中的按需加载
通过 babel-plugin-import 转译后的按需加载
按需加载打包方式调研:
以 SFC .vue 单文件方式编写组件,打包时利用
vue-template-compiler
手动将文件分离为template、js、css,利用 babel 系列工具将 template 与 js 输出为 js 文件,利用 less/sass、postcss 等工具输出 css 文件,如 wand-ui;模板和 js 部分使用 jsx/tsx 编写,css 样式分离,可以进行较小成本地手动打包,如 vant-ui
充分利用已有的 loader,将按需加载的组件认为是多个 entry,利用 webpack 进行多入口打包,如 element-ui、nut-ui
...
实践:
webpack.prod.demand.js 关键代码段
参考:
https://webpack.docschina.org/configuration/externals/#externals
https://github.com/youzan/vant
https://github.com/ElemeFE/element
开发体验
思考:能不能通过程序来一键完成上述操作?
eslint + prettier 保存时自动格式化,美化代码格式
本地站点搭建,保证自己的代码在生产环境下正确执行
参考:
https://github.com/jprichardson/node-fs-extra
https://github.com/prettier/eslint-plugin-prettier
https://github.com/ishen7/koa2-connect-history-api-fallback
使用体验
参考:
https://github.com/QingWei-Li/vue-markdown-loader
https://github.com/zenorocha/clipboard.js
https://github.com/ant-design/babel-plugin-import
利用 verdaccio 搭建 npm 私服
编写 npm 包必然要测试包的发布、引用是否正常, verdaccio 是一个很不错的选择,一键搭建。期待达到的效果,安装各种包一定快,并且通过一定的配置,找不到的依赖包会去外网下载。
后续
单文件打包过大,如 location 组件打包后 js 大小为 96k,如何解决?
分析:一部分代码来源于 babel 编译时插入的垫片代码,一部分代码来源于 import 进来的包的代码。
解决:移除 babel 中的垫片代码,利用 ts 的代码编译将代码编译为 ES5;打包时剔除 npm 包的依赖代码,在项目中运行时再安装,最终压缩后 js 大小为 25k。
代码压缩后 Vue 组件找不到文件名,导致组件注册失效?
分析:官方说明当 Vue 组件不设置 name 时默认会取类名作为组件名,但测试的两种打包方式(全局打包、按需打包)均出现不指定 name 名抛错的问题。
解决:
commit 的时候进行 eslint 校验,因为本地安装了 nvm 进行 node 版本,导致提交时校验代码抛错,又考虑目前保存代码时已进行代码校验,放缓该方案的实践。
The text was updated successfully, but these errors were encountered: