组内负责的几个项目都有一些一样的公共组件,所以就着手搭建了个公共组件开发脚手架,第一次开发 library,所以是参考着 iview 的配置来搭建的。记录如何使用webpack4搭建一个library的脚手架

前言

使用 webpack4,需要安装 webpack 和 webpack-cli

```yarn add webpack webpack-cli -D
```

然后就是书写配置文件。

项目脚手架结构

我写的 library 的目录结构如下,仅供参考,主要是模仿 iview 的结构,其中部分配置参考了 vue-cli的 webpack 配置文件。


  1. ├─build
  2. build.js // 用于执行构建
  3. check-versions.js // vue-cli 留下的,主要就是检查npm版本和node版本
  4. webpack.base.conf.js // 通用配置
  5. webpack.dev.conf.js // 开发环境
  6. webpack.dist.prod.conf.js // 用于生成library的代码 -- hbf.min.js
  7. webpack.prod.conf.js // 用于生成example文件的打包代码,这个其实是没有必要的.

  8. ├─dist
  9. └─example // example生成的打包文件夹,可以通过githubPage来预览,或者本地使用anywhere预览
  10. hbf.min.js // library 文件

  11. ├─example // example目录
  12. App.vue
  13. index.html
  14. main.js

  15. ├─lib
  16. index.js // 全量引入公共组件,并暴露出来,包含install方法可供vue引入使用该插件
  17. README.md

  18. └─components // 公共组件

  19. ├─package.json // 项目包依赖

更为具体信息的可以到github仓库阅览。

经过 webpack 编译后的代码

为了更好的理解,先来了解下 webpack 编译后的代码。

经过webpack处理过的代码通常都是如下所示


  1. // webpack编译后的代码
  2. /*
  3. * @param {Array} modules
  4. */
  5. ;(function(modules) {
  6. function __webpack_require__(moduleId) {
  7. var module = {
  8. i: moduleId, // 模块ID
  9. l: false,
  10. exports: {}, // 作为结果返回.
  11. }
  12. // 调用modules数组的某个元素(类型为函数)
  13. modules[moduleId].call(
  14. module.exports,
  15. module,
  16. module.exports,
  17. __webpack_require__
  18. )
  19. return module.exports
  20. }
  21. return __webpack_require__(0)
  22. })([
  23. /** 省略了代码, 该数组的每一项代表一个模块,实际是一个函数,接受三个参数,module对象,module.exports对象,__webpack_require__函数 **/
  24. ])

webpack 编译后的代码的整体结构就是一个IIFE函数,接受一个 modules: Array参数。

对于模块处理,无论是 ES Moduleimport 还是 commonjsrequire 都转化为__webpack_require__ 这个函数来引入模块。

__webpack_require__ 函数,会从 modules 数组的第一个元素开始(moduleId 为 0,也就是入口文件),执行该模块(实为一个函数)的逻辑,利用传入的module.exports的数据类型为引用类型Object,间接地给module.exports添加属性。

return __webpack_require__(0)

从入口文件开始,逐个引入依赖模块,最后返回入口模块的 module.exports

此时这个编译后的 js 文件,是无法被其他模块所引用的,只在当前作用域内有效, webpack 就提供了创建 library 的方式,就是在output里定义librarylibraryTarget。使得构建完的 js 可以供其他模块引入使用。

设置library配置

对于作为一个 library 使用的项目来说,output 选项需要设置 library


  1. // webpack.dist.pord.conf.js
  2. output: {
  3. path: path.resolve(__dirname, '../dist'),
  4. publicPath: '/dist/',
  5. filename: 'hbf.min.js',
  6. library: 'hbf',
  7. libraryTarget: 'umd'
  8. },

library 可以是字符串,也可以是对象,(对象仅限于 libraryTarget 的值为 umd 的情况下使用)


  1. output: {
  2. library: {
  3. root:'Hbf', // 暴露给全局变量,window.Hbf进行调用
  4. commonjs: 'hbf-public-components'
  5. },
  6. libraryTarget: 'umd'
  7. }

commonjscommonjs2 的区别。

commonjs 规范就是定义了一个 exports 对象,而 nodejs 在实现的时候,在 commonjs 规范的前提下做了一些扩展,定义了 module.exports ,从而也叫这种为 commonjs2 规范。

我们在引用别人的库的时候,通常都是可以通过多种方法引入的,比如 <script> 标签引入,通过 commonJS模块 引入,通过 ES6 Module 引入。

libraryTarget设置为umd(通用模块规范)的话,则打包后可以通过多种模块加载的方法加载 library,具有高兼容性。

library 的依赖问题

如果我们的 library 是基于某某库的基础上开发的,比如说写一个基于vue的 UI 组件库,在开发的这个组件库的时候,我们需要引入vue,如果使用这个组件库的用户本身就已经引入了vue,那么vue就会被引入并打包两次,所以我们在开发一个library的时候,对于一些所依赖的模块,可以由引入library的使用者提供。所以我们需要将依赖的模块在 library 的打包构建中去除。

externals 的作用,防止将某些 import 的包打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖。

通过设置 externals,从输出的 bundle 中排除 vueiview

这些外部依赖可能是以下的任何一种形式。

  • root 全局变量访问
  • commonjs 作为一个commonjs模块引入
  • commonjs2commonjs类似,不过导出的是 module.exports
  • amd 使用amd模块规范引入

  1. // webpack.dist.pord.conf.js
  2. externals: {
  3. vue: {
  4. root: 'Vue',
  5. commonjs: 'vue',
  6. commonjs2: 'vue',
  7. amd: 'vue'
  8. },
  9. iview: {
  10. root: 'iView',
  11. commonjs: 'iview',
  12. commonjs2: 'iview',
  13. amd: 'iview'
  14. }
  15. },

另外在 package.json 加多一个peerDependencies字段,作用是约定library所依赖的库的版本号,在使用者下载使用library的时候,如果所依赖的 iviewvue 的版本号不对,就会发出警告。


  1. "peerDependencies": {
  2. "iview": "&gt;2.0.0",
  3. "vue": "&gt;2.0.0"
  4. },

对于这两个依赖,写到开发环境依赖中 ,不然安装时,会在库的目录下安装vueiview,这也不符合让 library 的引用者提供 library 的依赖这个想法。

vue 插件库/ 组件库

对于 vue 的插件库 / 组件库来说,如果想要全局引入的话,需要有一个install方法。install 内部逻辑就是通过参数传进来的vue对象,注册所有组件。然后最后将所有公共组件连同install方法组成一个新对象暴露出去。


  1. // 引入公共组件
  2. import publicMenu from './components/public-menu'
  3. import tablePage from './components/table-page'
  4. import sliderCustom from './components/slider-custom'
  5. const components = {
  6. publicMenu,
  7. tablePage,
  8. sliderCustom,
  9. }
  10. const Hbf = Object.assign({}, components)
  11. const install = function(Vue, opts) {
  12. if (install.installed) return
  13. Object.keys(components).forEach(component =&gt; {
  14. Vue.component(component, component)
  15. })
  16. }
  17. // 用于script标签引入
  18. if (typeof window !== 'undefined' &amp;&amp; window.Vue) {
  19. install(window.Vue)
  20. }
  21. // 将install方法赋给Hbf对象
  22. Hbf.install = install
  23. // 输出default变量,用于全量引入,也可以在引入的时候选择使用 * 来全量引入
  24. export default Hbf
  25. // 输出各个组件,用于按需引入
  26. export { publicMenu, tablePage, sliderCustom }

在暴露含有 install 方法的对象时,一开始使用的是 module.exports,引用 library 的时候报错Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

因为我测试用的项目关闭了 babelES Module 的编译,通常情况下,没有手动关闭的话,babel 会将 ES6 Module 编译转换成 commonjs 规范。所以在关闭了module的转换的情况下,由于库的输出使用的是 commonjs 规范的 module.exports,而引入库使用的是 ES6 模块规范的import关键字,所以产生了报错,我将库的导出写成export关键字,就没报错了。

以后书写某一模块的导入导出,还是使用同一种规范的,不要混用。

了解到现在大多数库,都是用的 commonjs 规范,由于 webpacktree-shaking 只对 ES Module起作用,webpacktree-shaking 实际上是由Uglylify来实现的。

所以库的模块规范可以使用两种,利用 package.jsonmainmodule 字段分别定义库的两种模块规范的入口文件。 main 使用的是 commonjs 规范语法书写的文件,而 module 是使用 ES6 module 语法书写的文件,module字段目前还是一个提案。所以采用了 ES2015 模块语法的库的,当我们只使用到 library的部分代码,则可以利用webpack 进行 tree-shaking,去除未引用的代码,减少打包文件体积。

发布 npm 包

首先就是需要注册一个 npm 账号。

如果之前使用的是淘宝镜像的话,需要先切回 npm 官方源。不然是发不了包的。

打开命令行

切换官方源 npm config set registry https://registry.npmjs.org/

执行npm login,然后输入你的账号信息。

可以配置.npmignore 忽略一些不需要上传的文件,写法跟.gitignore 相同。

需要保证项目有正确的package.json文件和README.md文件

然后执行npm publish进行发包。

发完包就可以切回淘宝镜像源

npm config set registry https://registry.npm.taobao.org

引用组件库

Script 标签引入

一开始是将 JS 文件放在本地测试,发现在HTML文件的第一行就报错,Unexpected token <StackOverflow说的原因是引入路径不正确,所以我就把 JS 文件放到 CDN 上了,

```<script src="http://osuuzm0m8.bkt.clouddn.com/hbf.min.js"></script>
<script>
console.log(window.Hbf) // 会看到你导出的对象
</script>
```

输出

全量引用

可以像使用其他 vue 插件库/组件库一样使用。


  1. import hbf from 'hbf-public-components'
  2. // 使用use方法触发hbf的intall方法,注册全部组件
  3. Vue.use(hbf)

如果是没有导出default变量,则使用另外一种方式全量引入


  1. import * as hbf from 'hbf-public-components'

按需引用


  1. import { publicMenu } from 'hbf-public-components'

按需引用,如果 library 使用的是ES2015 Module规范,则不需要安装任何插件,webpack 会对其进行tree-shaking,去除未引用的代码。

前面提过,webpacktree-shaking是由Uglylify插件实现的,我在开发环境下,没有启用Uglylify来压缩代码,所以查看模块打包图,会发现整个库都被引入了,虽然我只引入了一个组件。webpack4在生产环境下,才会进行tree-shaking,设置mode的值为production就会开启生产环境下的优化。

如果是使用 commonjs 规范的 library 则需要一个插件支持,babel-plugin-import。该插件是ant官方开发的。许多 UI 组件库的按需引入也是依赖于这个插件。

安装

yarn add babel-plugin-import -D

修改.babelrc文件,


  1. "plugins": [
  2. ["import", {
  3. "libraryName": "hbf-public-components",
  4. "libraryDirectory": "lib/components"
  5. }]
  6. ],

总结

其实如果对于一个不是很稳定,需要一直迭代更新公用组件库来说,使用 npm 包的话,会比较不方便,经常更新的公共组件代码可以使用Git subtree教程)来维护,可以等到一定地步之后,公共组件库稳定下来之后再考虑发布一个 npm 包。

而开发一个组件库,也可以使用 rollup.js 来搭脚手架,rollup.js 默认使用的就是 ES2015 Module,可以进行静态分析,去除未引用的代码,tree-shaking 也是 rollup.js 先提出的。Rollup相比较于Webpack,更适合用于构建libraryVue.js就是使用 Rollup 构建的。Webpack 在代码分割这方面比较有优势,所以webpack 相对来说比较适合构建应用程序,不过使用 webpack 构建 library 也是可以的。

这个项目也可以用做 webpack 构建 library 的通用脚手架。下次再尝试Rollup构建 library

有帮助的话,可以给个star的话就最好啦。有问题也可以联系我。

项目地址: https://github.com/huya-base-...

npm 地址:https://www.npmjs.com/package...

关于模块规范,以及 webpack,babel 是如何编译转换模块的文章

来源:https://segmentfault.com/a/1190000015877664

使用webpack4搭建一个基于Vue的组件库的更多相关文章

  1. 手把手做一个基于vue-cli的组件库(上篇)

    基于vue-cli4的ui组件库,先贴个最终效果吧,步骤有点多,准备分上下篇,上篇:如何做一个初步的组件.下篇:编写说明文档及页面优化.开工. GitHub源码地址:https://github.co ...

  2. 手把手做一个基于vue-cli的组件库(下篇)

    基于vue-cli4的ui组件库,上篇:如何做一个初步的组件.下篇:编写说明文档及页面优化.接上篇,开工. GitHub源码地址:https://github.com/sq-github/sq-ui ...

  3. 新建一个基于vue.js+Mint UI的项目

    上篇文章里面讲到如何新建一个基于vue,js的项目(详细文章请戳用Vue创建一个新的项目). 该项目如果需要组件等都需要自己去写,今天就学习一下如何新建一个基于vue.js+Mint UI的项目,直接 ...

  4. 一个基于vue的仪表盘demo

    最近写了一个基于vue的仪表盘,其中 主要是和 transform 相关的 css 用的比较多.给大家分享一下,喜欢的话点个赞呗?嘿嘿 截图如下: 实际效果查看地址:https://jhcan333. ...

  5. 一个基于vue的时钟

    前两天写了一个基于vue的小钟表,给大家分享一下. 其中时针和分针使用的是图片,结合transform制作:表盘刻度是通过transform和transformOrigin配合画的:外面的弧形框框,啊 ...

  6. 创建一个自己的Vue UI组件库,并将它发布在npm上

    本文仅限于入门级,没有成规模制作,希望能对你有所帮助. 因为在开发多个项目中可能会用到同一个组件,那么我们通过复制粘贴的形式更新,无异于是笨拙的,我们可以通过上传到npm后,不断迭代npm包来实现更新 ...

  7. Uber如何搭建一个基于Kafka的跨数据中心复制平台 原创: 徐宏亮 AI前线 今天

    Uber如何搭建一个基于Kafka的跨数据中心复制平台 原创: 徐宏亮 AI前线 今天

  8. 快速搭建一个基于react的项目

    最近在学习react,快速搭建一个基于react的项目 1.创建一个放项目文件夹,用编辑器打开 2.打开集成终端输入命令: npm install -g create-react-app 3. cre ...

  9. 16款优秀的Vue UI组件库推荐

    16款优秀的Vue UI组件库推荐 Vue 是一个轻巧.高性能.可组件化的MVVM库,API简洁明了,上手快.从Vue推出以来,得到众多Web开发者的认可.在公司的Web前端项目开发中,多个项目采用基 ...

随机推荐

  1. ubuntu网络、包管理、工作内容小结

    中国地图 1.配置IP cat /etc/network/interfaces auto lo iface lo inet loopback # The loopback network interf ...

  2. AtCoder - 3913 XOR Tree

    Problem Statement You are given a tree with N vertices. The vertices are numbered 0 through N−1, and ...

  3. Android-Service基本用法、AIDL、Binder连接池详解

    本文介绍Service与Activity之间的通信,文章包含以下内容: 一.Service基本用法 二.通过AIDL实现Service与Activity跨进程通信 三.Binder连接池 四.使用Me ...

  4. [Servlet&amp;JSP] 从JSP到Servlet

    JSP与Servlet是一体的两面,JSP最后都会被容器转译为Servlet源码,自己主动编译为.class文件,载入.class文件然后生成Servlet对象. 由容器转译后的Servlet类具有_ ...

  5. C++静态库与动态库深入研究

    什么是库 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常. 本质上来说库是一种可执行代码的二进制形式,可以被操 ...

  6. 英特尔和Red Hat合作实现Gnome桌面的Wayland支持

    在发布支持XMir的Linux图形驱动程序xf86-video-intel 2.99.901后数天,英特尔宣布撤回对XMir的支持,XMir补丁不会合并到上游项目.XMir是Mir显示服务器的X11兼 ...

  7. Struts2使用POI创建Excel并下载

    本文将讲解在Struts2框架下如何使用POI创建Office Excel文档并实现下载功能. Apache POI ,操作微软文档的Java API,简单来说就是可以用来操作Office文档的API ...

  8. linux下apache https 虚拟主机配置

    如果单纯仅仅想在数据传输时加密传输,那么ssl证书是不须要认证的,可是浏览器打开时会有警告信息.如果我们做的不是一个公众产品那么也还好啦. 例如以下是今天学习时的一个笔记,事实上我用的是真实环境. 环 ...

  9. bvlc_reference_caffenet.caffemodel

    #uncoding:utf-8 # set up Python environment: numpy for numerical routines, and matplotlib for plotti ...

  10. crm操作销售订单实体

    using System;     using Microsoft.Xrm.Sdk;     using Microsoft.Xrm.Sdk.Query;     using Microsoft.Cr ...