最近萌发要做一个自己的基于
Vue
的组件库的想法,尝试下自己去造一些轮子;于是翻了下业内的标杆element-ui
组件库的设计,在此基础上去构建自己的ui库(站在巨人的肩膀上成功)。注:项目是基于[email protected]的;
element
的目录设计├─build // 构建相关的脚本和配置
├─docs // 打包后的静态文件
├─examples // 用于展示Element组件的demo
├─lib // 构建后生成的文件,发布到npm包
├─packages // 组件代码
| ├── button
| | ├── button.vue //组件文件
| | └── index.js // 导出Button组件以及处理供按需加载的方法
| ├── .. 各个组件
| └── theme-chalk // 这里存放所有的组件样式.scss
├─public // index.html
└─package.json
注:这里的设计将组件跟样式进行了分离,这样做的目的是为了更好的去做按需加载
我们都知道在vue中组件的安装要使用Vue.use(install)
,install
是向外暴漏的一个方法,里面调用Vue.component(component.name, component)
完成组件的注册;具体的源码如下:
/* eslint-disable */
// This file is auto gererated by build/build-entry.js
import FeButton from './button'
import FeInput from './input'
const version = '0.0.46'
const components = [
FeButton,
FeInput
]
const install = Vue => {
components.forEach(Component => {
Vue.use(Component);
// Vue.component(component.name, component) 也可使用这个进行注册
})
Vue.prototype.$message = Message
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export {
install,
version,
FeButton,
FeInput
}
export default {
install,
version
}
install
内部之所以能够使用Vue实例是Vue.use
中进行了Vue实例的参数的合并,有兴趣的可以去看看源码这里就提上一嘴;
以上基本的组件构造就完成了,我们也就很好的完成了第一步可在main.js
全局导入one-piece-ui的组件进行测试;这时候我们应该思考下如何去实现按需加载的问题啦!
import FeUI from '../packages';
import '../packages/theme-chalk/index.css';
Vue.use(FeUI);
element-ui
的介绍,借助 babel-plugin-component
,我们可以只引入需要的组件,以达到减小项目体积的目的。然后,将 .babelrc 修改为:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
这个插件的作用是什么呢?就是将引用路径进行了变换,如下:
import { Button } from 'one-piece-ui'
转换为:
var button = require('one-piece-ui/lib/button')
require('one-piece-ui/lib/theme-chalk/button.css')
这样我们就精准地引入了对应 lib 下的 Button 组件的 JS 和 CSS 代码了,也就实现了按需引入 Button 组件。
element-ui
的package.json
的scripts
构建命令"bootstrap": "yarn || npm i",
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
"build:umd": "node build/bin/build-locale.js",
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
"deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",
"deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",
"dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",
"dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
"dist": "npm run clean && npm run build:file && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
"i18n": "node build/bin/i18n.js",
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
"test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"
是不是有点懵逼,这都是啥咋这么多??? 我的内心其实也是很这啥都啥...
我对其中的一些命令进行了删除保留了一些 暂时我们能用到的,如下:
"init": "npm install commitizen -g && commitizen init cz-conventional-changelog --save-dev --save-exact && npm run bootstrap",
"bootstrap": "npm install && cd ./packages/theme-chalk && npm install",
"build:style": "gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
"build:docs": "vue-cli-service build",
"build:lib": "node build/build-lib.js",
"build:entry": "node build/build-entry.js ",
"serve": "vue-cli-service serve",
"clean": "rimraf lib && rimraf packages/*/lib",
"deploy": "sh build/deploy.sh",
"lint": "vue-cli-service lint",
"lib": "vue-cli-service build --target lib --name feui --dest lib packages/index.js && webpack --config ./build/webpack.component.js"
首先解释一下npm run init
做的那几件事:
git commit
的相关规范插件,主要是在功能提交上做一些规定npm run bootstrap
npm run bootstrap
做的几件事:
cd ./packages/theme-chalk
目录下,安装gulp相关的依赖;注:
element-ui
采用了gulp对scss
文件进行打包;个人觉得还是挺好的,gulp比较简单不像webpack的配置那么复杂,能够很简单的处理scss
build:style
做的几件事:
lib
目录下cp-cli
插件将打包好的lib
文件夹内容复制到lib/theme-chalk
目录下到此我们就完成了样式的打包及输出到指定的目录啦,此处有掌声...
npm run lib
命令时候,你会发现组件的js都被单独打包出来了,那这是如何实现的呢?我们看下这条命令webpack --config ./build/webpack.component.js
,对,就是他了;源码如下:const path = require('path');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const Components = require('./get-components')();
const entry = {};
Components.forEach(c => {
entry[c] = `./packages/${c}/index.js`;
});
const webpackConfig = {
mode: 'production',
entry: entry,
output: {
path: path.resolve(process.cwd(), './lib'),
filename: '[name].js',
chunkFilename: '[id].js',
libraryTarget: 'umd'
},
resolve: {
extensions: ['.js', '.vue', '.json']
},
performance: {
hints: false
},
stats: 'none',
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}, {
test: /\.vue$/,
loader: 'vue-loader'
}]
},
plugins: [
new ProgressBarPlugin(),
new VueLoaderPlugin()
]
};
module.exports = webpackConfig;
很熟悉是吧,对!就是你想的那样---多文件入口打包;这里里面有个get-components
工具方法就是返回所有的packages
下的组件文件名称;这样我们就可以通过命令自动注入组件的js文件啦~
const fs = require('fs');
const path = require('path');
const excludes = [
'index.js',
'theme-chalk',
'mixins',
'utils',
'fonts',
'.DS_Store'
];
module.exports = function () {
const dirs = fs.readdirSync(path.resolve(__dirname, '../packages'));
return dirs.filter(dirName => excludes.indexOf(dirName) === -1);
};
npm run build:entry
这里会执行build/build-entry.js
,源码如下:const fs = require('fs-extra');
const path = require('path');
const uppercamelize = require('uppercamelcase');
const Components = require('./get-components')();
const packageJson = require('../package.json');
const version = process.env.VERSION || packageJson.version;
const tips = `/* eslint-disable */
// This file is auto gererated by build/build-entry.js`;
function buildPackagesEntry() {
const uninstallComponents = ['Message'];
const importList = Components.map(
name => `import ${uppercamelize(name)} from './${name}'`
);
const exportList = Components.map(name => `${uppercamelize(name)}`);
const installList = exportList.filter(
name => !~uninstallComponents.indexOf(`${uppercamelize(name)}`)
);
const content = `${tips}
${importList.join('\n')}
const version = '${version}'
const components = [
${installList.join(',\n ')}
]
const install = Vue => {
components.forEach(Component => {
Vue.use(Component)
})
Vue.prototype.$message = Message
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export {
install,
version,
${exportList.join(',\n ')}
}
export default {
install,
version
}
`;
fs.writeFileSync(path.join(__dirname, '../packages/index.js'), content);
}
buildPackagesEntry();
不难发现,这里给我们自动化生成了一个packages/index.js
文件,是不是感觉技术减少人力? 心里:"卧槽..."
3. npm run build:lib
,通过上面的学习,到此这个命令其实也就不神秘啦,里面也就是做了一件汇总的事情:
/**
* Build npm lib
*/
const shell = require('shelljs');
const signale = require('signale');
const { Signale } = signale;
const tasks = [
'bootstrap',
'lint',
'clean',
'build:entry',
'lib',
'build:style'
];
tasks.forEach(task => {
signale.start(task);
const interactive = new Signale({ interactive: true });
interactive.pending(task);
shell.exec(`npm run ${task} --silent`);
interactive.success(task);
});
好了,基本的组件js分离功能也就完成了;
这里就不介绍了,具体请戳项目如何发布到NPM;
静态页面如何发布到GithubPages