关于javascript:用webpack从0到1打包一个按需加载的vue组件库

2次阅读

共计 7100 个字符,预计需要花费 18 分钟才能阅读完成。

在 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.js
import 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.js
const 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 myLib
npm link

在测试项目根目录下执行npm link my-lib

cd LibTest
npm 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 yyy
export 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-uitheme-chalk 都是可配置的,button是组件名,libbabel-plugin-component 插件默认要去寻找组件的文件夹。

如果咱们也想借助 babel-plugin-component 插件实现比拟优雅的按需援用,就把每个组件独自打包,放到组件库的lib 文件

实现按需援用

创立一个 webpack.component.js,作为组件独自打包的配置文件。再写两个组件 Hello 和 Test,前面用于测试。

因为是组件独自打包,所以每一个组件都导出一个函数,再 Vue.use(xx)的时候执行这个函数,实现组件的全局注册。

Hello/Hello.vueHello/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 中。

  • 第一步、批改 entryoutput字段:

    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-uibabel-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.js
import {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 打包一个库,下次见。

正文完
 0