本文转载于思否社区专栏:前端开发
作者:Vict911


不少前端开发者学习到一定阶段都会去封装一些自己的组件并将其开源。笔者在学习过程中发现,发布的资源包基本分为两类:

*打包发布的资源包
*非打包发布的资源包

那么这两种方式有何不同呢,他们的使用场景如何,具体的操作步骤又是怎么样的。本文将基于这两种发布方式,对比他们的不同,详解其步骤,并对在封装 Vue 组件和发布 npm 依赖过程中所遇到的问题进行归纳和解答。相信对于更加全面的了解前端工程化和 npm 发布流程会有些许帮助。
本文将涉及到:

``` *待封装的 Vue 组件(组件) *需引用的第三方依赖(第三方依赖) *测试使用的 Vue 项目(项目)为了后文不至混淆,将分别使用括弧中内容指代,请读者注意区分。##   **打包发布**这种方式是将封装好的组件最终打包成一个 js 文件发布。这种方式使得发开和调试时更接近与一个前端项目。但是一旦引用图片等静态资源需通过 BASE64 方式打包到 js,而对于字体一类较大的静态资源则根本无法被引用。  适用范围:没有或极少的依赖第三方插件、图片的组件的封装或 JS 方法的封装###   ###**1\. 项目结构******使用 webpack 构建前端工程,此处注意 webpack 配置文件应当区分开发和打包。 base.js、dev.js 和 build.js 通过 merge 语句链接。
    // 项目结构      module          |——hide          |   |——asset          |   |——components           // 组件包文件夹          |   |   |——index.js         // 组件包打包入口          |   |   └——componentA.vue  // 组件          |   |——pages                // 供开发时预览的页面          |   |——router          |   |——template             // html 模板          |   |——tools          |   |——app.vue              // 供开发时预览的 vue 入口          |   └——main.js              // 供开发时预览的项目入口          |——webpack                  // webpack 配置文件夹          |   |——base.js              // 通用配置          |   |——dev.js               // 开发用配置          |   └——build.js             // 打包用配置          |——package.json          └——README.md
###**2\. 配置 webpack**####   ####  base.js只放基础配置如 module 和通用的 plugins,不要包含打包入口,和出口。####   #### dev.js *需要配置预览相关的页面,因此入口应该为最外层 main.js,这个入口文件挂在了配置了路由信息的 app.vue。 *因为开发环境是起本地服务,因此无需出口信息。 *同时需要配置 htmlWebpackPlugin 和 WebpackDevServer 服务相关。
    var webpack = require('webpack');      config.entry = {          app:[path.join(APP_SRC, 'main.js')],          vendors: [              'vue',              'vuex',              'vue-router',              'axios'          ]      },      config.entry.app.unshift("webpack-dev-server/client?http://localhost:8088/")      config.plugins.push(new htmlWebpackPlugin({          hash: true,          minify: {              removeComments: true,              collapseWhitespace: true,          },          // favicon: path.join(APP_SRC, '/asset/images/ico.ico'),          template: path.join(APP_SRC, '/template/index.html'),      }))      var compiler = webpack(config);      var serve = new WebpackDevServer(compiler, {          quiet: false,          stats: {              colors: true          },          compress: true, //gzip 压缩          publicPath: 'http://localhost:8088/',          contentBase: '../dist/', // 默认情况下,webpack-dev-server 会从项目的根目录提供文件,可以通过此选项设置文件的目录名          historyApiFallback: true, // 当设置为 true 时,访问所有服务器上不存在的文件,都会被重定向到 /,也就是 index.html 文件      }).listen(8088);
#### build.js *组件的入口为 components 目录下的 index.js (index.js 引入并导出我们需要封装的组件)。 *出口信息应当包含引包时所使用的名字,组件名不能出现大写。
    var config = require("./base.js");      module.exports = merge(config, {          entry: [path.join(APP_SRC, '/components/index.js')],          output: {              path: APP_DIST,              filename: 'index.js',              library: 'module_name', // 指定的就是你使用 require 时的模块名              libraryTarget: 'umd', // 指定输出格式              umdNamedDefine: true // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define          },          devtool: false,          mode: 'production',      });
###**3\. 修改 package.json**package.json 中需要包含引包的入口,包名称和版本号。项目在引用组件时会根据 main 找到组件入口。
    {        "name": "my_module",        "version": "1.0.0",        "main": "dist/index.js"        ...      }

###**4\. 组件开发******组件开发无需多说,只要执行 npm run dev 将所需封装的组件引入到预览页面中开发调试即可。  组件入口 component/index.js 需引入并导出组件,更加详细请看下一章节。###   ###**5\. 打包发布******####**维护版本******手动修改 package.json 中的 version 或者执行 npm version patch 生成迭代一个版本。  ####**执行打包命令**npm run build  ####**登陆 npm**注意这一需要登陆官方仓库,如果之前连接的是淘宝镜像需要线切换回来。下面是查看仓库源和切换仓库的命令。 

npm config get registry // 查看仓库源
npm config set registry https://registry.npm.taobao.org
npm config set registry http://registry.npmjs.org

登陆 npm,输入账号、密码、邮箱

    npm login

#### 发布

npm publish

非打包发布

封装好的组件不经过打包,直接发布,用户在引用时不是引用一个独立的 js 文件,而是用过一个入口文件引用一些列的文件。这种方式因为不是在单独引用一个 js 所以可以完美解决第三方插件的引用、图片、字体等静态文件的引用但是对于组件项目本身而言开发时的预览调试方法则需要另寻他路。而且笔者暂时没有想到不适用测方法的,是笔者认为较为完美的封装发布方案。

1. 项目结构

**
**因为无需打包,因此不再需要 webpack 配置,项目结构也更加精炼。可以将组件的入口已到根目录。

    // 项目结构      module          |——hide          |   |——asset          |   |——components           // 组件包文件夹          |   |   |——componentA       // 组件 A          |   |   └——componentB.vue   // 组件 B          |   └——tools          |——index.js                 // 组件入口          |——package.json          └——README.md

###**2\. package.json******组件的名字、版本、入口文件这三个属性必备之外,需要注意的是 dependencies 中记录的第三方依赖都会在执行 npm install 的时候被安装。  

{
"name": "my_module",
"version": "1.2.16",
"main": "index.js",
"dependencies": {
"view-design": "^4.0.2",
...
}
...
}

3. 入口文件 (index.js)

引入组件

**
**使用正确的相对路径将组件和静态文件引入到 index.js

    import './hide/asset/css/index.less'      import ComponentA from './hide/components/ComponentA.vue'      import ComponentB from './hide/components/ComponentB.vue'

####**导出用于全局安装的对象******如果你想通过 Vue.use(MyComponent) 的方式使用组件,需要导出一个包含了所有组件的对象,并给这个对象定义 install 方法供 Vue.use() 方法调用。 

let ModuleObj = {
ComponentA,
ComponentB
}
let MyModule = {}
MyModule.install = (Vue) => {
for (let i in ModuleObj) {
Vue.component(i, ModuleObj[i])
}
}

export default MyModule

导出多个组件支持按需加载

    export {          ComponentA,          ComponentB      }
###**4\. 组件开发******####**关于引用第三方依赖******以引用 iview 为例,在组件 (components/ComponentA.vue)内部局部注册 iview 组件。这样相当于按需加载有助于减小最终项目打包的体积。


####**关于引用静态文件**使用正确的相对路径引用 css、图片或字体,如果之前习惯使用 webpack 的 alias 路径别名,此时当然也是不生效的。  ###**5\. 发布******无需打包,但同样需要维护版本,直接发布####  ####****####**维护版本******手动修改 package.json 中的 version 或者执行 npm version patch 生成迭代一个版本  ####**登陆发布******

npm login

 npm publish

6. 引用和开发调试

**
**因为组件项目不再有供开发调试的特面,因此需要在引用组件的项目中对组件进行调试,安装组件之后,在 node_module 中找到组件的所在的文件夹,进入修改即可完成调试。最后将调试好的组件文件夹拷贝出来,除 package.json 单独维护,发布即可。
两种引用方式:

    // 全局安装      import MyModule from 'my_module'      Vue.use(MyModule)      // 按需加载      import { ComponentA, ComponentB } from 'my_module'      Vue.component('ComponentA', ComponentA)      Vue.component('ComponentB', ComponentB)

总结

```

封装 Vue 组件并发布到 npm——完美解决组件中的静态文件引用
对比两种方式,笔者更倾向于后者,但只要理解了前端项目引用依赖的原理就会发现其实两者本质上都是一样的,读者可根据自己的需要选择一种方式进行封装。文中如有错误也希望各位不吝赐教及时指出。


点击左下角阅读原文,欢迎到SegmentFault 思否社区和文章作者展开更多互动和交流。

- END -
封装 Vue 组件并发布到 npm——完美解决组件中的静态文件引用