构建组件库的目录结构
│ .gitlab-ci.yml(使用gitlab持续集成持续部署)
│ babel.config.js(babel配置文件)
│ package.json
│ postcss.config.js(postcss预处理配置文件)
│ README.md(说明文档)
├─build(构建配置目录)
│ │ build-pkg.js(构建组件库的入口,生成lib文件夹)
│ │ build-site.js(构建文档官网入口)
│ │ createCpt.js(使用命令行生成待开发的组件)
│ │ dev-server.js(本地开发入口)
│ │ test-server.js(本地测试生成的组件库lib)
│ ├─configs(webpack配置文档)
│ │ package-all.config.js(全量构建组件库配置文件)
│ │ package-base.config.js(构建组件库的基础配置文件)
│ │ package-disperse.config.js(分包构建组件库的配置文件,提供按需加载)
│ │ site-base.config.js(构建文档官网的基础配置文件)
│ │ site-dev.config.js(构建文档官网的开发配置文件)
│ │ site-prod.config.js(构建文档官网的生产配置文件)
│ │ test-lib.config.js(测试生成的lib的配置文件)
│ │ test-unit.config.js(单元测试配置文件)
│ ├─test(单元测试启动入口)
│ │ setup.js
│ └─utils(工具目录)
│ check-version.js(检测node npm版本)
│ dic.js(目录函数)
│ ip.js(获取本地ip)
├─docs(文档官网的指南部分的md文档)
│ i18n.md
│ intro.md
│ lazy.md
│ theme.md
├─plugin(插件目录)
│ └─mdToVue(md转vue文件)
│ contrast.js
│ index.js
├─lib(最终生成的组件库目录,使用npm publish发布到npm)
├─site(文档官网,一个多入口的vue应用)
│ ├─demo(文档官网的demo部分,模拟的手机)
│ │ app.vue
│ │ index.html
│ │ index.js
│ │ router.js
│ └─doc(文档官网的主体部分)
│ │ app.vue
│ │ favicon.ico
│ │ index.html
│ │ index.js
│ │ left-nav.vue
│ │ md.less
│ │ root.js
│ │ router.js
│ ├─assets
│ │ code.png
│ │ copy.png
│ │ qrcode.png
│ ├─page
│ │ i18n.vue
│ │ intro.vue
│ │ lazy.vue
│ │ theme.vue
│ └─view
│ button.vue
├─src(组件库源码)
│ │ config.json
│ │ index.js
│ │ index.less
│ ├─assets(静态文件目录)
│ ├─locale(国际化)
│ │ │ index.js
│ │ └─lang
│ │ en-US.js
│ │ zh-CN.js
│ ├─mixins(混入)
│ │ bem.js(使用bem方式构建样式)
│ │ i18n.js(国际化啊入口)
│ ├─packages(组件目录)
│ │ ├─button
│ │ │ │ button.jsx
│ │ │ │ demo.vue
│ │ │ │ doc.md
│ │ │ │ index.js
│ │ │ │ index.less
│ │ │ │
│ │ │ └─__test__
│ │ │ button.spec.js
│ ├─style(基础样式)
│ │ base.less
│ │ hairline.less
│ │ var.less
│ └─utils(工具库目录)
│ constant.js
│ deep-assign.js
│ index.js
│
└─test(测试构建lib的项目)
构建具体步骤
使用npm run dev启动开发(site文件夹)
- 构建入口build/dev-server.js
- doc部分:md转化成vue,作为site/doc依赖
- docs/*.md => site/doc/page/*.vue
- src/packages/**/*.md => site/doc/view/*.vue
- demo部分:会直接引用src里面的组件作为依赖
//多页面构建入口 site-dev.config.js
entry:{
demo: ROOT_PATH('site/demo/index.js'),
doc: ROOT_PATH('site/doc/index.js')
},
output:{
filename:'js/[name].bundle.js',
chunkFilename:'js/[name].chunk.js'
},
new mdVue({
entry:ROOT_PATH('docs'),
output:ROOT_PATH('site/doc/page'),
cache: false,
needCode: false
}),
new mdVue({
entry:ROOT_PATH('src'),
output:ROOT_PATH('site/doc/view'),
cache: false
}),
使用npm run site构建生产环境的dist包
- 使用site-prod.config.js将site目录代码转换成可部署到生产环境的dist包
使用npm run pkg构建组件库
- 全量构建 package-all.config.js
entry: {
zmmui: ROOT_PATH('src/index.js')
},
output:{
path:ROOT_PATH('lib'),
filename:'zmmui.js',
library:'[name]',
libraryTarget:'umd',
umdNamedDefine: true,
globalObject: 'this'
},
- 分包构建 package-disperse.config.js
const entry = {}
confs.packages.map((item) => {
let cptName = item.name.toLowerCase()
entry[cptName] = ROOT_PATH(`src/packages/${cptName}/index.js`)
})
entry: entry,
output:{
path:ROOT_PATH('lib/packages'),
filename:'[name]/[name].js',
library:'[name]',
libraryTarget:'umd',
umdNamedDefine: true,
globalObject: 'this'
},
MardDown文件转化成vue文件的webpack插件
//遍历文件夹下面的文件
const nodeFilelist = require('node-filelist');
// 高亮插件
const hljs = require('highlight.js');
// markdown转换成html标签
let marked = require('marked');
// 监听文件变化
let Chokidar = require('chokidar');
// 计算目录文件的hash
let { hashElement } = require('folder-hash');
let rendererMd = new marked.Renderer()
rendererMd.heading = (text,level) => {
// 标题标签的处理逻辑
}
rendererMd.code = (code, infostring) => {
// code代码的处理逻辑
}
marked.setOptions({
tables: true,
renderer: this.rendererMd,
});
- mdToVue/contrast.js:通过计算缓存文件的hash值,当文件变化时计算并返回需要转换的md文件
- 当渲染器的heading处理逻辑发生变化时,必须要使用marked.setOptions重置渲染器
按需加载插件babel-plugin-sep-import
// 用来生成一个特定类型的ast语法
const t = require('@babel/types')
module.exports = function() {
return {
visitor: {
//遍历import语法
ImportDeclaration(p, {opts = {}}) {
let libraryName = opts.libraryName || 'zmmui'
let libraryDir = opts.libraryDir || 'lib/packages'
let style = opts.style || 'css'
const {node} = p;
// 如果节点的value值等于目标的libraryName,进行下一步操作
if(node.source.value && node.source.value == libraryName){
node.specifiers.forEach((item) => {
let cpt = item.imported.name
// 节点前插入处理
p.insertBefore(
// 根据规则生成一个新的import语法
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(cpt))],
t.stringLiteral(`${libraryName}/${libraryDir}/${cpt.toLowerCase()}/index.js`)
)
)
if(style === 'css'){
p.insertBefore(t.importDeclaration([], t.stringLiteral(`${libraryName}/${libraryDir}/${cpt.toLowerCase()}/${cpt.toLowerCase()}.css`)));
} else {
p.insertBefore(t.importDeclaration([], t.stringLiteral(`${libraryName}/${libraryDir}/${cpt.toLowerCase()}/index.${style}`)));
}
})
// 删除原节点
p.remove()
}
}
}
}
}
使用npm run cpt生成一个新的组件
const inquirer = require('inquirer') 命令行交互工具,保存用户输入
const parse = require('@babel/parser').parse 将js代码转换成抽象语法树
const traverse = require('@babel/traverse').default 遍历生成的抽象语法树
const generate = require('@babel/generator').default 将修改后的抽象语法树生成新的代码
自动化生成新的组件,以及动态修改、添加入口文件依赖项
单元测试以及代码覆盖率(npm run test:unit)
cross-env NODE_ENV=test nyc mochapack --webpack-config build/configs/test-unit.config.js --require build/test/setup.js src/packages/**/__test__/*.js --reporter=mochawesome
- --require build/test/setup.js 确保运行单元测试前使用setup提供测试环境
- --reporter=mochawesome 优化单元测试报告的输出
- mochapack mocha 与 webpack 结合的单元测试工具
- nyc 计算测试覆盖率
- jsdom-global node里面模拟jsdom
- 单元测试配置文件test-unit.config.js
rules: [
{
test: /\.js$|\.jsx$/,
use: [
'babel-loader',
{
// 使用istanbul-instrumenter-loader插入代码用于计算覆盖率
loader: 'istanbul-instrumenter-loader',
options: { esModules: true },
}
],
include: [ROOT_PATH('src/packages')],
},
...
使用这种方式计算覆盖率时,如果使用sfc的方式编写组件库,覆盖率一直是0,因此使用了jsx方式编写组件库,能正常计算出代码覆盖率
总结
- 通过这个项目学习了webpack的配置,babel插件的写法、ast语法树相关、webpack插件、单元测试以及测试覆盖率
- 祝大家元旦快乐!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!