pma934 / pma934.github.io

build by https://github.com/pma934/vuetify-blog
2 stars 0 forks source link

在npm上发布自己的vue组件库(使用npm install 或者 CDN的方式引用) #17

Open pma934 opened 5 years ago

pma934 commented 5 years ago

在codepen上看到了一个好看的pen,于是把它重写成了一个vue的组件; 为了能在其他地方使用该组件,准备创建一个自己的vue组件库发布到npm上; 因为之前没有发布过,于是就先上网上搜索了一下教程

主要是参考了一下这篇教程还有iviewelement这两个组件库的package.json、index.js、webpack文件。

只看创建流程可以直接看第三部分

一、npm publish

name:发布的包名,默认是上级文件夹名。不得与现在npm中的包名重复。包名不能有大写字母/空格/下滑线!
version:你这个包的版本,默认是1.0.0。对于npm包的版本号有着一系列的规则,模块的版本号采用X.Y.Z的格式,具体体现为:
  1、修复bug,小改动,增加z。
  2、增加新特性,可向后兼容,增加y
  3、有很大的改动,无法向下兼容,增加x
description:项目简介
mian:入口文件,默认是index.js
scripts:包含各种脚本执行命令
test:测试命令。
author:自己的账号名
license:开源文件协议
private:是否私有

image image

二、我们如何通过npm引用组件的

先看一下node.js中的模块调用的规则 https://nodejs.org/api/modules.html#modules_accessing_the_main_module

主要注意以下两部分 image

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with '/'
   a. set Y to be the filesystem root
3. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. If "main" is a falsy value, GOTO 2.
   c. let M = X + (json main field)
   d. LOAD_AS_FILE(M)
   e. LOAD_INDEX(M)
   f. LOAD_INDEX(X) DEPRECATED
   g. THROW "not found"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = [GLOBAL_FOLDERS]
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIRS + DIR
   d. let I = I - 1
5. return DIRS

以iview为例,我们npm i iview下载iview组件库后,通过import iView from 'iview'引用它,其实就是通过import iView from './node_modules/iview'引用它,然后./node_modules/iview是一个文件夹,会按照LOAD_AS_DIRECTORY(X)中的规则寻找,先是找到./node_modules/iview/package.json这个文件,然后找到其中的main字段,最后通过main字段找到./node_modules/iview/dist/iview.js这个文件。 image iview.js这个文件是通过webpack打包得到的,直接看代码基本上是看不懂的,我们看看它是怎么打包出来的。


先找到iview.js的webpack配置文件

https://github.com/iview/iview/blob/2.0/build/webpack.dist.dev.config.js

……
process.env.NODE_ENV = 'production';
module.exports = merge(webpackBaseConfig, {
    devtool: 'source-map',
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/dist/',
        filename: 'iview.js',
        library: 'iview',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    ……
});

上面是在生产环节下打包iview.js的配置,我们找到它的入口文件

https://github.com/iview/iview/blob/2.0/src/index.js

import Affix from './components/affix';
……

const components = {
    Affix,
    ……
};

const iview = {
    ...components,
    iButton: Button,
    ……
};

const install = function(Vue, opts = {}) {
    if (install.installed) return;
    ……

    Object.keys(iview).forEach(key => {
        Vue.component(key, iview[key]);
    });

    ……
};

// auto install
if (typeof window !== 'undefined' && window.Vue) {
    install(window.Vue);
}

const API = {
    ……
    install,
    ……
};

……

module.exports.default = module.exports = API;   // eslint-disable-line no-undef

从该文件可以看出来,它先是将components文件夹中的组件都通过import引入,然后定义了一个components对象来存放所有组件,然后又对部分组件增加了第二个key定义了iview对象(例如<Col><Col>组件,也可以通过<i-col><i-col>来使用),然后定义了一个install函数来遍历注册组件。最后将install函数作为输出对象的install方法。

然后这个输出对象其实就是上面import iView from 'iview'得到的iView对象,因此通过Vue.use(iView)来调用iView.install方法就能成功的把iView库的组件注册到我们的Vue对象上。

另外,如果是直接通过CDN引用js文件的话,会触发auto install注释下的内容,会将组件注册到当前window.Vue上(需要先通过CDN引入vue.js)

三、流程

  1. 使用vue init webpack-simple xxx初始化项目
  2. npm install
  3. 在src目录下新建components文件夹,并将组件放入其中
  4. 在根目录下新建index.js
import XXX from './src/components/XXX'
import YYY from './src/components/YYY'
import ZZZ from './src/components/ZZZ'
……

const components = [
  XXX,YYY,ZZZ,……
];

const install = function (Vue) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

export default {
  install,
  XXX,
  YYY,
  ZZZ,
  ……
};

这里说明一下,我是用组件内部的name属性做的组件标签名

  1. 配置webpack.config.js
var path = require('path')
var webpack = require('webpack')
const NODE_ENV = process.env.NODE_ENV

module.exports = {
  //入口这里一个是测试组件时npm run dev的入口文件,一个是npm run build的入口文件
  entry: NODE_ENV === 'development' ? './src/main.js' : './index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/', //输出的文件夹
    filename: 'filename.js',//输出的文件名
    library: 'objectname',    //引用js文件时创建的全局变量名
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  ……
}

……
  1. 配置package.json
{
  "name": "package-name",
  "description": "描述",
  "version": "1.0.0",
  "author": "XXXXXXXXXXX",
  "license": "MIT",
  "private": false,
  "main": "dist/filename.js",
  ……
}
  1. npm run build
  2. npm publish
  3. 使用
//npm导入
npm i package-name

import xxx from 'package-name'
Vue.use(xxx)

//cdn导入
<script src="//vuejs.org/js/vue.min.js"></script>
<script src="//https://unpkg.com/package-name@1.0.0/dist/filename.js"></script>
  1. 如果想把css单独抽离出来还需要更新webpack、webpack-cli,和安装optimize-css-assets-webpack-plugin、uglifyjs-webpack-plugin等包。