沐光

记录在前端之路的点点滴滴

发布一个 vue 包

前言

文章用来纪录我如何从零开始,通过 webpack4 发布一个 vue 的 npm 包,同时使用的是 es6 语法。涉及到的知识点包括:

  • webpack4
  • npm 包的发布
  • babel 配置
  • vue 配置

准备工作

了解相关知识

首先需要了解相关基础知识,由简单到难,依次列举如下:

  1. 如何发布一个 npm 包,需要准备些什么?
  2. webpack 简单的打包配置需要哪些
  3. 如何配置 babel 的 es6 支持
  4. 如何配置 vue 的支持
  5. 如何配置 vue 的 @、@src 等引用
  6. 包内的 vue 的组件该如何编写
  7. 如何优化打包的体积

发包前的准备

创建账号

发布 npm 包首先需要一个 npm 账号。npm 添加账号的方法:

1
2
3
4
5
npm adduser
## 之后会提示你输入 userName 和 password
## ...
## 保存后登陆
npm login
创建待发布的包

创建包的方法也十分简单,此处用 yarn 来创建一个 npm 包

1
2
3
# 首先进入对应文件夹,然后
yarn init
# 之后根据提示输入项目名称、版本等内容即可
规范化 package.json 内容

当然,要发布一个比较规范的包,仅默认的一些配置还是不够的,package.json 的详细配置可见「中文文档」,总结过后,我们可以配置这么一些字段:

  • name: 项目名称
  • version: 当前包的版本
  • keywords: 关键词,方便搜索
  • description: 项目描述
  • repository: 项目地址
  • author: 作者
  • contributors: 项目开发人员
  • files: 项目包括的文件
  • scripts: webpack 打包会用到
  • style: 项目样式所存放的地址
  • license: 许可证(一般 MIT 即可)

安装时常用的有此三种依赖:

  • dependencies: 必须依赖的包,会在用户安装此包时,将该部分的包一并装上,会更新版本(如果版本过低)
  • peerDependencies: 会在用户安装此包时,如果该模块内的包没有或者用户安装的对应包版本过低,会用 warning 提示用户安装(不会自动安装)
  • devDependencies: 开发时会用到的包,打包至项目的或者 webpack 配置需要的等等,不会帮用户安装或者给予提示

它们依次对应的指令是:

1
2
3
4
5
6
7
8
# dependencies
yarn add xxx

# peerDependencies
yarn add xxx -P

# devDependencies
yarn add xxx -D

初始化包的配置

两种安装方式

1
2
3
4
5
# 全局安装
npm i -g webpack webpack-cli

# 项目安装
yarn add webpack webpack-cli -D

在先前创建的项目内新建一个 webpack.config.js 文件,并写入以下基础内容

1
2
3
4
5
6
7
8
9
10
const path = require('path');

// 处理路径
function resolve(dir) {
return path.join(__dirname, dir);
}

module.exports = {
mode: 'production',
};

注:webpack4 版本这两个是分开的,都得装

配置 webpack

出入口文件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在 module.exports 文件内添加 entry 和 output 两字段
module.exports = {
// ...
entry: {
<自定义名>: <文件地址>
},
output: {
path: <输出地址>,
publicPath: <虚拟路径,server 路径有关>,
filename: <输出的文件名>,
library: <包名>,
libraryTarget: <库的支持,一般用 umd 即可>,
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 例子
module.exports = {
entry: {
'test': './src/index.js'
},
output: {
path: resolve('lib'),
publicPath: '/lib/',
filename: '[name].js',
library: 'test',
libraryTarget: 'umd'
},
}
loader 配置
vue-loader

由于项目是关于 vue 的,因此可参考「vue-loader 官网」,跟着对应内容配置即可,解析 vue 的 loader 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// rules 部分
{
test: /\.vue$/,
loader: 'vue-loader'
}

// plugins 部分
plugins: [
new VueLoaderPlugin()
]

// resolve 部分
resolve: {
// 设置别名
alias: {
vue$: 'vue/dist/vue.esm.js',
'@': resolve('./src'),
'@mixins': resolve('./src/mixins'),
'@base': resolve('./src/base'),
},
// 文件后缀扩展识别(导入时自动加后缀)
extensions: ['*', '.js', '.vue']
},
es6 支持

首先在 webpack 配置文件的 rules 部分(vue-loader 配置的部分)添加 js 的语法转译

1
2
3
4
5
6
7
8
9
// ...
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src')],
exclude: [resolve('node_modules')],
},
];

然后在项目内添加 .babelrc 文件,并在内部添加 es 支持

1
2
3
4
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}

此处使用的是最新版本的 babel 配置,官网有详细的讲解

需要安装的一些依赖:

1
2
3
4
5
6
7
8
9
// devDependencies 部分
"@babel/core": "^7.4.3",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.4.3",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^7.1.2"

// dependencies 部分
"@babel/runtime": "^7.4.3"

注意: babel-core 默认安装的是 6.x 版本,装 7 版本的命令是: yarn add babel-core@^7.0.0-bridge.0 -D

css 样式支持

由于 vue 项目中用的较多的是 less 或者是 sass,因此打包时需要对样式进行转译,此时需要样式相关的 loader。webpack 的配置例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// rules 部分
{
test: /\.(le|sc|sa|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
],
include: [ resolve('src') ]
}

// plugins 部分
plugins: [
// 提取 CSS 至单一文件夹
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: '[id].[hash].css',
})
]

这里注意到最顶部配置的不是 style-loader/vue-style-loader,当我们使用的是开发环境即:"mode": "development" 时,使用 style-loader/vue-style-loader,当使用生产环境即:"mode": "production" 时,我们使用此处的配置,以达到最小化压缩

其余资源支持

参考官方文档总结的配置如下:

1
2
3
4
5
6
7
8
9
10
11
{
test: /\.(png|jpeg|jpg|gif|svg)$/i,
loader: 'url-loader',
options: {
limit: 8192,
fallback: 'file-loader',
name: '[name].[ext]?[hash]',
outputPath: 'images/',
publicPath: ''
}
}

url-loader 转为 base64 格式,对于小文件类型用这种方法处理更优,但是大文件最好还是用 file-loader 处理,因此对此 loader 设定一个限制,大于阈值 8K 的自动用 file-loader 处理

文档传送门

optimization 配置

此部分是最后对文件进行压缩优化一下体积,webpack4 中生产环节相对较好的 devtool 模式是 cheap-module-source-map,但是压缩出的文件内会包含 source-map,如果想将其去掉需要额外的一些配置。webpack 配置例子:

1
2
3
4
5
6
7
8
9
10
11
12
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
// 最快压缩模式
compress: false,
mangle: true
}
}),
new OptimizeCSSAssetsPlugin({})
],
}

此处使用的是 UglifyJsPlugin 压缩 js 代码,删除 map 部分的内容, OptimizeCssAssetsPlugin 则是压缩 CSS 部分的内容,具体配置可参考对应的 npm 插件文档

注:css 部分有推荐配合使用 cssnano,因为本项目 css 内容就 2 属性,因此不做过多的配置了,有需要的可查阅相关知识配置即可

附带的配置
  • clean-webpack-plugin: 放在 plugins 的最后面,可每次自动清除对应的生成文件
  • webpack-bundle-analyzer: 对压缩的文件的依赖树的可视化分析,方便分析是否有多余依赖

vue 的配置

发布一个 vue 组件时需要为组件添加 install 方法,每个组件的文件目录树大致为

1
2
3
4
fileName
|-- index.js
|-- src
| -- component

这是一个组件的文件结构,其中的 component 就是我们的 vue 组件,index.js 为向外侧暴露该组件的一个入口文件,其内容基本格式一致,例如:

1
2
3
4
5
6
7
8
import myComponent from './src/component';

// 添加单独引用的 install 方法
myComponent.install = function (Vue, options = {}) {
Vue.component(myComponent.name, myComponent);
};

export default myComponent;

整体的配置可以参考 elementUI 的组件写法,原理一样,本质是如何添加 install 方法

开发时的一些疑问

关于 devtool 的选用

开发环境推荐: cheap-module-eval-source-map

生产环境推荐:cheap-module-source-map

理由:

  • 大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息,所以我们使用 cheap 模式可以大幅提高 souremap 生成的效率
  • 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)
  • 使用 eval 方式可大幅提高持续构建效率,参考 webapck devtool 速度对比列表,这对经常需要边改边调的前端开发而言非常重要
  • 直接将 sourceMap 放入打包后的文件,会明显增大文件的大小,不利于静态文件的快速加载;而外联 .map 时,.map 文件只会在 F12 开启时进行下载( sourceMap 主要服务于调试),故推荐使用外联 .map 的形式

升级 babel 的问题

依照官方文档的介绍,安装了 @babel/core 等插件,并更新了 .babelrc 的配置,但是 build 的时候报错表示 babel-core 版本过低(直接 yarn add babel-core 装的是 6.x 版本),需要 7.0.0.0 版本,因此需要更新 babel-core

1
yarn add babel-core@^7.0.0-bridge.0 -D

本地调试包

一般通过 yarn link 来调试编写的包,但是除此之外还有另外一个方法,通过相对路径来安装对应的包

1
"target-file": "file:<relative-path>"

但是缺点是,每次更新后都需要删除后重新安装此包,或者将包升级一下版本,然后再次安装才能调试,比较麻烦。

最好的配置方法还是通过 yarn link 来调试。

按需引入比解构引入打包的体积要大?

分析了一下打包后的代码,就用以下的例子来解释

1
2
3
4
// 按需
import debouce from 'lodash/debounce';
// 解构
import { debounce } from 'lodash';

通过 webpack-bundle-analyzer 分析打包后的内容,发现前者有 lodash 的模块,后者没有,后者没有的原因时在压缩的代码内有 require('lodash') 即,我们需要配置 package.jsonlodashdependencies 依赖,需要引用此包的项目有对 lodash 的依赖,也就是说,解构获取的 debouce 不会将对应的内容连同代码一起打包,而是需要用户安装对应的包的依赖,因此体积相对会变小。

为什么要用 library?

使用 umd 是为了兼容 commonjs 和 amd 的方式,一般发包用 umd 可保证不会出啥问题,兼容性也很棒,而使用 umd 则需要配置 library。

path 和 publicPath

一般我们配置好 path 其实就能万事大吉了,那 publicPath 究竟有什么用呢?其实简单理解, path 至当前的项目的根目录(本地),publicPath 指的是服务器的根目录(服务器),比如:将资源放在 CDN 上时,把 publicPath 设置为 CDN 的值就行了。

参考文档

附录

完整样例