mg4355娱乐mg手机版 > www.4355.com >

所以就着手搭建了个公共组件开发脚手架

图片 1

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

前言

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

yarn add webpack webpack-cli -D

然后就是书写配置文件。

项目脚手架结构

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

├─build│ build.js // 用于执行构建│ check-versions.js // vue-cli 留下的,主要就是检查npm版本和node版本│ webpack.base.conf.js // 通用配置│ webpack.dev.conf.js // 开发环境│ webpack.dist.prod.conf.js // 用于生成library的代码 -- hbf.min.js│ webpack.prod.conf.js // 用于生成example文件的打包代码,这个其实是没有必要的.│├─dist│ └─example // example生成的打包文件夹,可以通过githubPage来预览,或者本地使用anywhere预览│ hbf.min.js // library 文件│├─example // example目录│ App.vue│ index.html│ main.js│├─lib│ │ index.js // 全量引入公共组件,并暴露出来,包含install方法可供vue引入使用该插件│ │ README.md│ │ │ └─components // 公共组件│├─package.json // 项目包依赖

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

经过 webpack 编译后的代码

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

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

// webpack编译后的代码/** @param {Array} modules*/;(function(modules) { function __webpack_require__(moduleId) { var module = { i: moduleId, // 模块ID l: false, exports: {}, // 作为结果返回. } // 调用modules数组的某个元素 modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ) return module.exports } return __webpack_require__(0)})([ /** 省略了代码, 该数组的每一项代表一个模块,实际是一个函数,接受三个参数,module对象,module.exports对象,__webpack_require__函数 **/])

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

对于模块处理,无论是ES Module的import还是commonjs的require都转化为__webpack_require__这个函数来引入模块。

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

return __webpack_require__(0)

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

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

设置library配置

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

// webpack.dist.pord.conf.jsoutput: { path: path.resolve(__dirname, '../dist'), publicPath: '/dist/', filename: 'hbf.min.js', library: 'hbf', libraryTarget: 'umd'},

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

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

commonjs和commonjs2的区别。

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

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

libraryTarget设置为umd(通用模块规范)的话,则打包后可以通过多种模块加载的方法加载 library,具有高兼容性。 关于libraryTarget详细要点可以参考webpack官方文档

library 的依赖问题

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

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

通过设置externals,从输出的bundle中排除vue和iview。

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

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

// webpack.dist.pord.conf.jsexternals: { vue: { root: 'Vue', commonjs: 'vue', commonjs2: 'vue', amd: 'vue' }, iview: { root: 'iView', commonjs: 'iview', commonjs2: 'iview', amd: 'iview' }},

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

// package.json"peerDependencies": { "iview": "2.0.0", "vue": "2.0.0"},

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

vue 插件库/ 组件库

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

// 引入公共组件import publicMenu from './components/public-menu'import tablePage from './components/table-page'import sliderCustom from './components/slider-custom'const components = { publicMenu, tablePage, sliderCustom,}const Hbf = Object.assign({}, components)const install = function(Vue, opts) { if (install.installed) return Object.keys(components).forEach(component = { Vue.component(component, component) })}// 用于script标签引入if (typeof window !== 'undefined'  window.Vue) { install(window.Vue)}// 将install方法赋给Hbf对象Hbf.install = install// 输出default变量,用于全量引入,也可以在引入的时候选择使用 * 来全量引入export default Hbf// 输出各个组件,用于按需引入export { publicMenu, tablePage, sliderCustom }

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

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

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

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

发布 npm 包

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

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

打开命令行

切换官方源npm config set registry

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

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

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

然后执行npm publish进行发包。

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

npm config set registry

引用组件库Script 标签引入

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

script src=""/scriptscript console.log(window.Hbf) // 会看到你导出的对象/script

输出

全量引用

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

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

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

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

按需引用

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

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

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

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

安装

yarn add babel-plugin-import -D

修改.babelrc文件,

"plugins": [ ["import", { "libraryName": "hbf-public-components", "libraryDirectory": "lib/components" }]],

总结

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

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

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