精华 打包 Vue 组件库的正确姿势
发布于 1 个月前 作者 jingsam 793 次浏览 来自 分享

为了方便其他开发者使用组件库,开发的 Vue 组件库在发布之前需要对其打包。本文基于 Webpack 讨论打包 Vue 组件库的正确方法。

选择正确的打包格式

首先,我们必须明确组件库的使用场景。有些场景是直接使用 <script> 在 HTML 中引入,有些场景是使用打包工具在后台构建。作为组件库,应该兼容这些使用场景。组件库应该保持中立,不应该限定于某种使用方式或者打包工具。例如,虽然 Webpack 很流行,组件库不能声明只支持 Webpack 方式使用,忽略了其他选择。原因在于,打包工具并不只有 webpack,还有 browserify、rollup 等。另外,前端工具发展很快,今年流行的工具明年可能就没人用了,你肯定不希望你的组件库会随着某个工具不流行而消逝吧。回顾下曾经流行的 grunt、glup,一大堆基于它们的插件随着工具本身的不流行而被扔进了垃圾箱。

为了支持多种使用场景,我们需要选择合适的打包格式。常见的打包格式有 CMD、AMD、UMD,CMD只能在 Node 环境执行,AMD 只能在浏览器端执行,UMD 同时支持两种执行环境。显而易见,我们应该选择 UMD 格式。Webpack 中指定输出格式的设置项为 output.libraryTarget,其支持的格式有:

  • “var” - 以一个变量形式输出: var Library = xxx (default);
  • “this” - 以 this 的一个属性输出: this[“Library”] = xxx;
  • “commonjs” - 以 exports 的一个属性输出:exports[“Library”] = xxx;
  • “commonjs2” - 以 module.exports 形式输出:module.exports = xxx;
  • “amd” - 以 AMD 格式输出;
  • “umd” - 同时以 AMD、CommonJS2 和全局属性形式输出。

以下是 webpack.config.jsoutput 设置的示例:

output: {
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/dist/',
    filename: 'iview.js',
    library: 'iview',       // 模块名称
    libraryTarget: 'umd',   // 输出格式
    umdNamedDefine: true    // 是否将模块名称作为 AMD 输出的命名空间
}

到此,我们解决了组件库输出的问题。

如何打包组件依赖

前一篇文章中我们讨论了组件库实质上是 Vue 的插件,Vue 应该是组件库的外部依赖。组件库的使用者会自行导入 Vue,打包的时候,不应该将 Vue 打包进组件库。

在 webpack 中,我们可以将 Vue 设置为 externals,以避免将 Vue 打包进组件库,相应的设置如下:

externals: {
    vue: 'vue'
}

啊哈,我们搞定了组件依赖问题。至此,读者可能很皱起眉头开始埋怨我了:这么简单的问题,查下 webpack 文档不就得了,还用得着我啰里啰嗦地写这么多!

事实上,问题往往没有我们想得那么简单!如果你将打包后的组件库以 <script> 标签形式直接引入,你会发现并不能正常执行,提示 vue 未定义。

为了分析问题,我们将打包的代码前几行拿出来看看:

(function webpackUniversalModuleDefinition(root, factory) {
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory(require("vue"));
    else if(typeof define === 'function' && define.amd)
        define("iview", ["vue"], factory);
    else if(typeof exports === 'object')
        exports["iview"] = factory(require("vue"));
    else
        root["iview"] = factory(root["vue"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_157__) {
    // ....
})

我们可以看见,打包后的代码以 4 种形式声明了 Vue 依赖:

  1. module.exports = factory(require("vue")) - commonjs2 形式;
  2. define("iview", ["vue"], factory) - AMD 形式;
  3. exports["iview"] = factory(require("vue")) - commonjs 形式;
  4. root["iview"] = factory(root["vue"]) - 全局变量形式。

<script> 标签形式使用组件时,会同样使用 <script> 标签导入 Vue。Vue 导入的变量是 “window.Vue” 而不是 “window.vue”,因此会出现 vue 未定义的错误。

幸好,webpack 可以为各种导入形式设置不同名称,设置如下:

externals: {
    vue: {
        root: 'Vue',
        commonjs: 'vue',
        commonjs2: 'vue',
        amd: 'vue'
    }
}

再次打包,你可以发现打包的组件库不管是 <script> 标签方式还是后端构建,都可以正常工作了。

最后,帖一个打包 iView 组件库的 webpack 配置:

var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/dist/',
        filename: 'iview.js',
        library: 'iview',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    externals: {
        vue: {
            root: 'Vue',
            commonjs: 'vue',
            commonjs2: 'vue',
            amd: 'vue'
        }
    },
    resolve: {
        extensions: ['', '.js', '.vue']
    },
    module: {
        loaders: [{
            test: /\.vue$/,
            loader: 'vue'
        }, {
            test: /\.js$/,
            loader: 'babel',
            exclude: /node_modules/
        }, {
            test: /\.css$/,
            loader: 'style!css!autoprefixer'
        }, {
            test: /\.less$/,
            loader: 'style!css!less'
        }, {
            test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
            loader: 'url?limit=8192'
        }, {
            test: /\.(html|tpl)$/,
            loader: 'vue-html'
        }]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: '"development"'
            }
        })
    ]
}
回到顶部