参考:
初始化项目
vue init webpack-simple yyl-npm-practice
//然后根据提示
cd yyl-npm-practice
npm install
npm run dev
为什么不使用vue init webpack npm-practice初始化项目,因为开发组件不需要太多的配置,配置过多会引起配置麻烦,使用webpack-simple足够。
文件目录配置
参考了一下 element ,适合多插件使用,怎么建立文件夹放自己的组件完全取决于你自己,最后只要在webpack的入口和出口配置对应的即可。
├── src/ // 源码目录
│ ├── packages/ // 组件目录
│ │ ├── myButton/ // 组件1
│ │ ├── myButton.vue // 组件代码
│ │ ├── index.js // 挂载插件
├── myText/ // 组件2
│ │ ├── myText.vue // 组件代码
│ │ ├── index.js // 挂载插件
│ ├── App.vue // 页面入口
│ ├── main.js // 程序入口
│ ├── index.js // (所有)插件入口
├── index.html // 入口html文件
按需引入,即单组件引入
packages/myButton/myButton.vue:
<template>
<div>
MyButton组件
</div>
</template>
<script>
export default {
name: 'MyButton',//注意要填写,不然多组件时候进行遍历的时候就无法自动获取组件名称
}
</script>
packages/myButton/index.js:
import MyButton from './myButton.vue'
export default {
//Vue为vue的构造函数,options为可选配置项
install(Vue,options){
Vue.component(MyButton.name, MyButton);
}
}
以上为止一个组件插件就已经封装成功,最后我们测试发布到npm上即可。
测试使用: main.js引入:
import Vue from 'vue'
import App from './App.vue'
//引入组件使用Vue.use时候会自动执行install方法,从而挂载了组件到Vue构造函数上
import MyButton from './packages/myButton/index.js'
Vue.use(MyButton)
new Vue({
el: '#app',
render: h => h(App)
})
App.vue:
<template>
<div id="app">
<my-button />
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
显示:
多组件情况下,全局引入所有组件
我们在src目录下,即App.vue同级下创建index.js.
src/index.js:(所有组件的入口)---elementui使用的也是这样形式:
import MyButton from './packages/myButton/myButton.vue'
import MyText from './packages/myText/myText.vue'
const components = [
MyButton,
MyText
// ...如果还有的话继续添加
]
// 这一步判断window.Vue是否存在,因为直接引用vue.min.js, 它会把Vue绑到Window上,我们直接引用打包好的js才能正常跑起来。
if (typeof window !== 'undefined' && window.Vue) {
components.map(component => {
Vue.component(component.name, component);
})
}
export default {
//Vue为vue的构造函数,options为可选配置项
install(Vue,options={}){
components.map(component => {
Vue.component(component.name, component);
})
}
}
//效果等价于
// const install = function(Vue,options={}) {
// components.map(component => {
// Vue.component(component.name, component);
// })
// }
// export default {
// install
// }
可以使用动态引入方式取代多个import,优化后:
//使用动态引入方式取代import方式引入
//找到当前同级目录下的packages文件夹,true为为为有文件夹就往下找,/\.vue$/为找到所有以.vue名称结尾的文件,返回的是一个webpackContext对象
const urlList = require.context('./packages',true,/\.vue$/);
console.log('=======',urlList.keys())
//["./myButton/myButton.vue", "./myTable/myTable.vue", "./myText/myText.vue"]
//使用urlList('./myButton/myButton.vue').default;即可实现import一样效果,获取组件实例。
// 这一步判断window.Vue是否存在,因为直接引用vue.min.js, 它会把Vue绑到Window上,我们直接引用打包好的js才能正常跑起来。
if (typeof window !== 'undefined' && window.Vue) {
urlList.keys().forEach(item=>{
let compObj = urlList(item).default;//也可以直接根据./myButton/myButton.vue进行切割获取对应的myButton等,而上面每个组件里面都存在有name属性,因此可以直接使用name属性
Vue.component(compObj.name,compObj)
})
}
export default {
//Vue为vue的构造函数,options为可选配置项
install(Vue,options={}){
urlList.keys().forEach(item=>{
let compObj = urlList(item).default;
Vue.component(compObj.name,compObj)
})
}
}
测试 src/main.js:
import Vue from 'vue'
import App from './App.vue'
import AllComponents from './index'
Vue.use(AllComponents)
new Vue({
el: '#app',
render: h => h(App)
})
src/App.vue:
<template>
<div id="app">
<my-button />
<my-text />
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
显示:
使用vue的jsx写法写组件
npm i npm i babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props -D
//.babelrc
{
"presets": [
["env", { "modules": false }]
],
"plugins": ["transform-vue-jsx"]//添加这个插件
}
使用:
//test.js
export default {
name: 'Test',
props: {
show:{
type: String
}
},
render(){
return (
<div>
{this.show?'jsx':'xxx‘}
</div>
)
}
}
发布前操作,账号注册和配置
- 配置修改: webpack.config.js配置开发环境和生产环境入口:
// ... 此处省略代码
// 执行环境
const NODE_ENV = process.env.NODE_ENV
module.exports = {
// 根据不同的执行环境配置不同的入口
entry: NODE_ENV == 'development' ? './src/main.js' : './src/index.js',
output: {
// 修改打包出口,在最外级目录打包出一个index.js 文件,我们 import 默认会指向这个文件
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'outy-test.js',
library: 'outy-test', // 指定的就是你使用require时的模块名
libraryTarget: 'umd', // libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的
umdNamedDefine: true // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define
},
// ... 此处省略代码
}
package.json:
private字段(private是true的时候不能发布到npm,需设置成false); 并增加main字段, main字段是require方法可以通过这个配置找到入口文件,这输入模块加载规范。并且查看name是否为重名,重名则需要换一个名称发布,因为发布时候不允许重名。如何查找名称是否被使用过,登录npm进行search查一下,如果查到内容说明存在过。
"name": "outy-test",
// 发布开源因此需要将这个字段改为 false
"private": false,
// 这个指 import outy-test 的时候它会去检索的路径
"main": "dist/outy-test.js",
index.html:
因为刚才我们更改webpack.config.js时候把输入文件的filename默认名称build.js更改为了outy-test.js因此需要对应上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>outy-test</title>
</head>
<body>
<div id="app"></div>
<!--原来的-->
<!-- <script src="/dist/build.js"></script> -->
<script src="/dist/outy-test.js"></script>
</body>
</html>
- 运行打包命令生成dist文件夹,
npm run build如果不需要生成该map文件,可以找到webpack.config.js文件注释掉图示内容。
发布到npm
- 首先注册npm账号
npm login,登录- 坑(参考):npm源如果为淘宝源会报500错误,必须修改为npm源才能登录:
npm config set registry https://registry.npmjs.org/
- 坑(参考):npm源如果为淘宝源会报500错误,必须修改为npm源才能登录:
npm publish,发布- 遇到的问题,注册的时候邮箱没验证,打开邮箱验证即可不会出现下面403错误。
发布成功的图示为:
使用
- 安装
npm i outy-test -S - mian.js引入
import OutyTest from 'outy-test'
Vue.use(OutyTest)
- 使用
<template>
<div class="home">
<my-button></my-button>
<my-text></my-text>
</div>
</template>
- 显示
更新已经存在的包
- 在package.json修改版本号
- 再运行
npm publish - 查看
删除指定版本的包
npm unpublish outy-test@1.0.0,npm unpublish 包名@版本号- 查看
删除整个包
npm unpublish 包名 --force
对element-ui的el-table进行二次封装
以webpack-simple为模板。
- 安装
npm i element-ui -S - 按需引入element-ui
npm install babel-plugin-component -D然后,将 .babelrc 修改为:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
- 安装loader和配置loader
npm i style-loader url-loader -D
//其他需要的loader
less-loader
sass-loader
- main.js引入
import { Table,TableColumn } from 'element-ui';
Vue.use(Table)
Vue.use(TableColumn)
webpack.config.js:
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: '[name].[ext]'
}
}
- 封装table组件 packages/myTable/myTable.vue:
<template>
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
export default {
name:'MyTable',
methods: {
tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
}
},
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}]
}
}
}
</script>
- 引入和使用Vue.compoment注册部分参考上面。
- 测试使用:
<div id="app">
<my-table />
</div>
elemetui封装的组件学习
tag.vue:
<script>
export default {
name: 'ElTag',
props: {
text: String,
closable: Boolean,
type: String,
hit: Boolean,
disableTransitions: Boolean,
color: String,
size: String,
effect: {
type: String,
default: 'light',
validator(val) {
return ['dark', 'light', 'plain'].indexOf(val) !== -1;
}
}
},
methods: {
handleClose(event) {
event.stopPropagation();
this.$emit('close', event);
},
handleClick(event) {
this.$emit('click', event);
}
},
computed: {
tagSize() {
return this.size || (this.$ELEMENT || {}).size;
}
},
render(h) {
const { type, tagSize, hit, effect } = this;
const classes = [
'el-tag',
type ? `el-tag--${type}` : '',
tagSize ? `el-tag--${tagSize}` : '',
effect ? `el-tag--${effect}` : '',
hit && 'is-hit'
];
const tagEl = (
<span
class={ classes }
style={{ backgroundColor: this.color }}
on-click={ this.handleClick }>
{ this.$slots.default }
{
this.closable && <i class="el-tag__close el-icon-close" on-click={ this.handleClose }></i>
}
</span>
);
return this.disableTransitions ? tagEl : <transition name="el-zoom-in-center">{ tagEl }</transition>;
}
};
</script>
card.vue:
<template>
<div class="el-card" :class="shadow ? 'is-' + shadow + '-shadow' : 'is-always-shadow'">
<div class="el-card__header" v-if="$slots.header || header">
<slot name="header">{{ header }}</slot>
</div>
<div class="el-card__body" :style="bodyStyle">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'ElCard',
props: {
header: {},
bodyStyle: {},
shadow: {
type: String
}
}
};
</script>
简单总结:
//如果使用为
<my-button @click="xxx" @change="bbb" size="small">
<span>默认插槽</span>
</my-button>
//组件应该要这样对应
+ 属性就必须有props进行接收,一定情况下还需要配合computed一起使用
+ 事件肯定有this.$emit('clcik',参数),this.$emit('change',参数)等
+ 有默认插槽,组件使用为<span v-if="$slots.default><slot></slot></span>