在vue我的项目开发中,咱们会将常常用到的逻辑或模块形象成组件,对于那些多个我的项目都有用到的组件,能够思考封装成组件库,公布到npm。每次要到只须要npm install xx
一下,就不必来回拷贝了。上面咱们就从0开始来打包一个vue组件库。
应用vue组件库的常见形式
- 1、通过
script
标签引入
<body> <div id="app"> <hello></hello> </div></body><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--在组件库之前引入vue--><script src="../dist/my-lib.js"></script><script> new Vue({ el: '#app' })</script>
2、通过
import
引入import Vue from "vue"import App from "./App.vue"import MyLib from "my-lib"Vue.use(MyLib)new Vue({ name: "root", el: '#root', render: h => h(App)})
这样引入后,在须要应用组件库的我的项目组件中,间接应用,比方:
<template><div id="app"> <hello></hello></div></template>
组件在应用之前都须要注册组件,注册组件能够部分注册和全局注册,从下面组件库的应用,咱们能够看出,组件库的组件都是全局注册的。
在第一种通过script
标签引入的形式中,咱们在组件库外部实现了全局注册,所以引入组件库就能够间接应用。
在第二种通过import
引入的形式中,咱们通过Vue.use(MyLib)
实现组件的全局注册。
组件库的我的项目构造如下,在src/index.js
中定义install办法
。
// src/index.jsimport Hello from "./components/Hello.vue"function install(Vue){ //内部的Vue.use(MyLib)会执行该办法,实现组件的全局注册。 Vue.component(Hello.name, Hello)}if(window && window.Vue) { //通过`script`标签引入的状况,在组件外部实现组件注册。 Vue.use(install)}export default install
webpack打包
webpack的根底应用: webpack由浅入深系列(一) 、webpack由浅入深系列(二)
当初咱们的组件库就一个hello组件
,咱们将通过webpack,将它打包成能够通过上述两种形式应用的组件库。
webpack打包vue组件库,跟打包一般的vue我的项目的配置绝大部分是一样的。只是output
有所不同,如下所示,减少了libraryTarget
等。
//webpack.config.jsconst path = require('path')const { VueLoaderPlugin } = require('vue-loader')module.exports = { mode: 'none', entry: './src/index.js', output: { path: path.join(__dirname,"/dist"), filename: 'my-lib.js', libraryTarget: 'umd', //用到的模块定义标准 library: 'myLib', //库的名字 libraryExport: 'default' }, module: { rules: [ { test: /\.vue$/, use: ['vue-loader'] }, { test: /\.css$/, use: ['style-loader','css-loader','postcss-loader'] }, { test: /\.s[ac]ss$/i, use: ['style-loader','css-loader','postcss-loader','sass-loader'] }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, } ] }, plugins: [ new VueLoaderPlugin() ]}
libraryTarget
libraryTarget 规定了打包出的库文件所用的模块定义标准,次要有上面这些:var
assign
this
window
global
jsonp
commonjs
commonjs2
amd
umd
。具体含意参考官网文档
咱们这里应用的是umd
,它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。在umd
模块标准下导出的库文件大体构造如下,咱们常常在一些插件或库文件中看到,比方jquery。
//官网示例。 'MyLibrary'就是library中定义的库名称, 对应咱们本人demo中的myLib。(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports['MyLibrary'] = factory(); else root['MyLibrary'] = factory(); })(typeof self !== 'undefined' ? self : this, function() { return _entry_return_; // 此模块返回值,是入口 chunk 返回的值});
写好了webpack.config.js
,咱们就来package.json
中配置scripts
:
"scripts": { "build": "webpack" //默认会执行根目录下的webpack.config.js},
npm run build
一个乞丐版的vue组件库就打包了。
externals
在这个咱们的vue组件库中,vue是作为参数传入的。咱们也没有import
其余库。当这个库的性能比较复杂时,往往会不可避免地import
其余库,比方你要应用Vue的静态方法Vue.xxx
,或者工具库,比方lodash。
这时咱们不可能把这些内部库打包到本人的组件库,因为,当用户本人也引入了这些内部库,再引入咱们的组件库,就会对外部库进行两次引入和打包,这齐全是冗余的。所以咱们在开发一个库的时候,对于库依赖的内部模块,能够由引入库的使用者提供。
这须要借助externals
,它能避免将某些 import 的包打包到bundle中,而是在运行时再去从内部获取这些内部依赖模块。在下面的webpack.config.js
中增加externals
:
const path = require('path')const { VueLoaderPlugin } = require('vue-loader')module.exports = { ... externals : { vue: { root: "Vue", //通过 script 标签引入,此时全局变量中能够拜访的是 Vue commonjs: "vue", //能够将vue作为一个 CommonJS 模块拜访 commonjs2: "vue", //和下面的相似,但导出的是 module.exports.default amd: "vue" //相似于 commonjs,但应用 AMD 模块零碎 } }}
externals的配置可承受string、array、object、function、regex等各种语法。externals配置
只有当libraryTarget: 'umd'
时,才能够配置如上那样的蕴含 { root, amd, commonjs,... } 的对象,其它的libraryTarget的值不能这样配置。
为了测试externals的配置,咱们在src/index.js
中退出上面两行代码:
import Vue from "vue"console.log(Vue)
怎么在本地调试组件库
1、先来调试一下通过
script
标签引入的状况,新建一个html文档,引入打包好的组件库引入。<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <div id="app"> <hello></hello> </div></body><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script src="../dist/my-lib.js"></script><script> new Vue({ el: '#app' })</script></html>
- 2、
import
引入的状况。搭建一个用于测试的vue我的项目LibTest,或者,更方便快捷一点的,在已有的vue的我的项目中加一个测试组件,并增加到routes中。
批改 myLib我的项目 的 package.json 中的 main 字段,main 字段定义了 npm 包的入口文件。
"main": "./dist/my-lib.js",
在myLib组件库我的项目根目录下执行 npm link
cd myLibnpm link
在测试项目根目录下执行npm link my-lib
cd LibTestnpm link my-lib //这个my-lib是在myLib我的项目的package.json中定义的"name": "my-lib"
而后,就能够和一般npm装置的组件库一样应用了
在测试项目LibTest的入口js中,引入my-lib
,执行Vue.use()
注册组件
import MyLib from 'my-lib'Vue.use(MyLib)
在LibTest的App.vue中应用组件:
<template> <div> <hello></hello> </div></template>
按需加载
根底的组件库咱们曾经跑通了,上面就把它革新成按需加载的组件库。
对于组件库我的项目,反对按需加载须要满足:组件库以 es6 模块化形式导出。也就是说,导出文件得是这样de导出导入形式:
import { xxx } from 'xxx'import yyy from yyyexport default zzz;export { a, b, c };
通过Tree-Shaking删除未应用的代码。
Tree-Shaking的原理,墙裂倡议认真浏览这两篇文章:
Tree-Shaking性能优化实际 - 原理篇 、 你的Tree-Shaking并没什么卵用
1、ES6的模块引入是动态剖析的,故而能够在编译时正确判断到底加载了什么代码。2、分析程序流,判断哪些变量未被应用、援用,进而删除此代码。
在根底打包中,咱们晓得webpack有多种导出模式(libraryTarget),然而webpack却没有反对导出ES模块的模式。大家通常都会抉择的'umd'导出模式,它的导出文件也是一个立刻执行函数,一点都不合乎es6 模块化形式。依照下面那样的配置是没有方法按需加载的。
有很多库为了按需援用,将每一个组件或者性能函数,都打包成独自的文件或目录。而后通过文件门路进行援用:
import 'echarts/lib/chart/pie'import 'echarts/lib/component/title'
这样写不能一次引入多个组件,也不够优雅。element-ui就专门开发了babel插件babel-plugin-component
,使咱们能像上面这样按需引入:
import { Button, Select } from 'element-ui'Vue.use(Button)Vue.use(Select)
参考element-ui的构建形式
babel-plugin-component
插件将:
import { Button } from 'element-ui'
转换成:
var button = require('element-ui/lib/button')require('element-ui/lib/theme-chalk/button.css')
在下面的门路中,element-ui
和theme-chalk
都是可配置的,button
是组件名,lib
是babel-plugin-component
插件默认要去寻找组件的文件夹。
如果咱们也想借助babel-plugin-component
插件实现比拟优雅的按需援用,就把每个组件独自打包,放到组件库的lib文件
。
实现按需援用
创立一个webpack.component.js
,作为组件独自打包的配置文件。再写两个组件Hello和Test,前面用于测试。
因为是组件独自打包,所以每一个组件都导出一个函数,再Vue.use(xx)的时候执行这个函数,实现组件的全局注册。
Hello/Hello.vue
和Hello/index.js
// src/index.js改成这样import Hello from "./components/Hello"import Test from "./components/Test"function install(Vue){ Vue.use(Hello) Vue.use(Test)}if(window && window.Vue) { Vue.use(install)}export default install
实现也很简略,外围就是多入口打包。这里有讲多入口打包
咱们将后面webpack.config.js
中的内容复制粘贴到webpack.component.js
中。
第一步、批改
entry
和output
字段:entry: {'hello': './src/components/Hello/index.js','test': './src/components/Test/index.js','my-lib': './src/index.js'},output: {path: path.join(__dirname,"/lib"),filename: '[name].js',libraryTarget: 'umd',library: '[name]',libraryExport: 'default'},
增加package.json的scripts:
"component": "webpack --config webpack.component.js"
npm run component
在myLib组件的目录下就多了一个lib文件夹。
第二步、从下面的
element-ui
的babel-plugin-component
插件应用形式,咱们看出组件库的js文件和css文件是拆散开来。所以咱们也将组件的css拆散到独自的文件,这里咱们用mini-css-extract-plugin
插件。const MiniCssExtractPlugin = require('mini-css-extract-plugin')
配置实现,再次npm run component
接下来,批改package.json中 "main": "./lib/my-lib.js",
回到测试项目。在我的项目根目录下创立.babelrc
(装置插件就依据命令行提醒进行装置),重启我的项目。 babel-plugin-component配置参考
{ "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "my-lib", "styleLibrary": { "name": "lib-style", // same with styleLibraryName "base": false // if theme package has a base.css } } ] ]}
咱们来粗犷地测试一下。只援用hello组件,同时应用hello和test。
// LibTest/src/index.jsimport { Hello } from "my-lib"Vue.use(Hello)// LibTest/src/App.vue<template> <div class="cp"> <hello></hello> <test></test> </div></template>
加上test组件的援用:
import { Hello,Test } from "my-lib"Vue.use(Hello)Vue.use(Test)
咱们再去掉test组件的援用。将测试项目打包。在打包后的文件中搜寻两个组件的内容。
按需加载的vue组件库咱们就打包实现了。其实,开发组件库也能够应用 rollup.js来打包,它能很好地反对ES Module,tree-shaking 也是 rollup.js 先提出的,实现组件库地按需加载就简略很多。明天就到这里啦,下一篇文章将会讲到怎么用rollup打包一个库,下次见。