前言
通过上节 自建vue组件 air-ui (10) -- vuepress 写文档 (进阶版) 其实关于 vuepress
写文档的事情,也基本上能说的也差不多了。本节主要是在这个基础上再进行一下补充,将一些在 air-ui
组件库写文档中遇到的一些奇葩的问题,以及怎么解决的,这个也列一下,做一下备忘录。
第三方库 Popper 的报错
之前有遇到一个很奇怪的情况,就是写autocomplete
demo 的时候,在本地运行中,是可以 work 的:
而且打包到另外一个项目用第三方库的方式引用,也是可以的:1
2
3
4
5
6
7
8
9
10<air-col :span="12">
<div class="sub-title">激活即列出输入建议</div>
<air-autocomplete
class="inline-input"
v-model="state1"
:fetch-suggestions="querySearch"
placeholder="请输入内容"
@select="handleSelect"
></air-autocomplete>
</air-col>
看错误,好像是读一个不存在的 Popper
属性,后面查了一下,发现在 popper.js
这个第三方库的时候,到 root
分支的时候,发现是 undefined
,导致报错:
这个 root
是 undefined
,所以会报错。 但是在项目中引用又是正常的, 估计是 vuepress
机制的问题???
后面查了一下,可能是这个问题 浏览器的 API 访问限制:
请注意,这并不能解决一些组件或库在导入时就试图访问浏览器 API 的问题 —— 如果需要使用这样的组件或库,你需要在合适的生命周期钩子中动态导入它们:
因为其实 root
就是浏览器的 window
对象,所以就是在初始化的时候,就访问 window
对象,但是这时候其实还没有被导入,所以可以这样子写:1
2
3
4
5
6
7
8
9<script>
export default {
mounted () {
import('./lib-that-access-window-on-import').then(module => {
// use code
})
}
}
</script>
但是这时候就会改到代码,变得跟 air-ui
里面的代码不一样了,所以我这边其实就是对这个引用做了一下兼容了:
加了这一行代码,如果 root 不存在的话,就将 window 赋给他, 这样就不会报错了。 然后再试一下:
发现界面正常了,但是点击输入框之后,又会报错???
看样子是PopperJS
对象不是函数?? 后面查了一下,发现是这边的问题:
这边的这个 PopperJS
变成对象了,不是函数了。 而这个是在这边赋值的:1
let PopperJS = Vue.prototype.$isServer ? function () {} : require('./popper');
这时候是赋值到 window 对象的时候,没有返回该对象导致的。 所以这边再进行兼容,如果这个对象不是函数的话,那么就将 window 的 Popper
属性赋给他:
这样子就可以兼容了, 然后再试一下, 这下子终于可以了:
第三方库 date.js 的报错
在写到 date-picker
也有出现另一个第三方库在项目dev环境没有问题,但是在接入 vuepress dev 环境的时候,有问题。就是 date.js
这个第三方库:
这个方式跟上面的 PopperJS
一样,这边就不多了,直接改代码, 在 date.js
里面补上:
然后在 data-util.js
也要改:1
2
3import fecha from './date';
/* todo 这边是新增,原来的 element ui 的代码没问题,但是如果在 vuepress 文档写 vue demo 的时候,有用到这个js的组件都会报错,原因是找不到 fecha 对象,所以这边就做一下兼容,并不会影响到原来组件的逻辑 -- by zach */
let fechaModel = fecha || window.fecha;
因为 ES6 import
导出来的变量 只能 read-only
, 不能再被赋值,因此这边要在初始化一个变量 fechaModel
来进行处理, 所以这个文件涉及到的相关引用都要将 fecha
改成 fechaModel
:1
2
3
4
5
6
7
8export const formatDate = function(date, format) {
date = toDate(date);
if (!date) return '';
return fechaModel.format(date, format || 'yyyy-MM-dd', getI18nSettings());
};
export const parseDate = function(string, format) {
return fechaModel.parse(string, format || 'yyyy-MM-dd', getI18nSettings());
};
然后用这个变量来处理就可以了。这样子 vuepress
才能 work:
vuepress build 报错
当我组件全部写完的时候,并且本地 vuepress
运行的时候,例子也没有问题的时候, 这时候我要打静态文件 build
的时候,报错了1
F:\code\air-ui>yarn docs:build
date.js
的 window
对象不存在,导致报错? 但是 vuepress
的本地环境是正常的啊?? 为啥 vuepress
本地环境可以,一旦打包到静态文件就不行 ???
尝试用 webpack 配置解决
首先我的想法是,是不是跟 air-ui
组件打包的时候一样, dev 环境下,其实都不用去管, 但是打 release 包的时候, 是要排除两个文件的:1
2
3
4
5
6{
test: /\.(jsx?|babel|es6)$/,
include: process.cwd(),
exclude: /node_modules|utils\/popper\.js|utils\/date.\js/,
loader: 'babel-loader'
},
这两个文件都是第三方库文件,在打 dist
包的时候,进行 babel
解析的时候,是要排除掉的。那么 vuepress
的 dist
模式是不是也要排除这种文件呢。这时候就要配置 webpack
配置了,后面查了一下,发现在 .vuepress/config.js
中是可以配置 webpack
的, 可以通过:1
2
3
4
5
6
7
8
9
10chainWebpack: config => {
},
configureWebpack: (config, isServer) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
} else {
// 为开发环境修改配置...
}
},
来配置,而且有两种方式,具体看文档这个:vuepress 配置webpack, 我用的是 chainWebpack
的方式来排除这个文件:1
2
3
4chainWebpack: config => {
config.module.rule('js')
.exclude.add('/src/utils/date.js')
},
但是发现这样做还是不行,还是照样报同样的错。
还是改代码
yarn docs:build
的时候会报 window is not defined
错误,是因为 date.js
这个第三方库引入了 window
对象, 而出现问题的原因是当开发 VuePress
应用时,由于所有的页面在生成静态 HTML
时都需要通过 Node.js
服务端渲染,因此需确保只在 beforeMount
或者 mounted
访问浏览器 / DOM 的 API
。
所以归根究底,就是在打 build 包的时候,这时候要生成静态 html 文件
,所以需要通过 Node.js
服务端渲染,所以这时候的环境就是服务端环境
(难怪 window
对象找不到)。那么既然是 node 环境
,那么肯定是有 global
对象的,因此我就把 window
改成 global 对象
,所以 date.js
中将 window 换成 global:1
2
3if(typeof main === "undefined"){
main = global;
}
后面真的打包成功了,而且不报错了。 但是发现打开页面的时候,报了这个错,
原来把 global
对象打进去了, 而 global
对象是没有在浏览器环境下的。 因此就报错了。这样就蛋疼了,build
的时候,没有 window
对象, 浏览器环境的时候,没有 global
。 那么有没有可能打包的时候,有办法区分是 node 环境呢?
是有的,因为我想起了,另一个第三方库 popper
,好像 build
的时候就没有报错?? 他也有引用 window 对象, 为啥他就没有报错呢???
后面看了一下他的引用代码,原来他真的有区分,如果是node环境下的编译的时候,是不进行这个js文件引用的,也就不会触发到 window 对象了。1
2
3
4
5let PopperJS = Vue.prototype.$isServer ? function () {} : require('./popper');
/* todo 这边是新增,原来的 element ui 的代码没问题,但是如果在 vuepress 文档写 vue demo 的时候,有用到这个js的组件都会报错,原因是找不到 root 对象,所以这边就做一下兼容,并不会影响到原来组件的逻辑 -- by zach */
if (typeof PopperJS !== 'function') {
PopperJS = window.Popper;
}
所以 Vue.prototype.$isServer
这个方法,可以帮助我们判断当前打包环境是不是 node 环境,从而可以不进行引用。因此我们也改成一样的:1
2
3
4
5
6
7
8
9import Vue from 'vue';
import { t } from '../locale';
// import fecha from './date';
/* todo 这边是新增,原来的 element ui 的代码没问题,但是如果在 vuepress 文档写 vue demo 的时候,有用到这个js的组件都会报错,原因是找不到 fecha 对象,所以这边就做一下兼容,并不会影响到原来组件的逻辑 -- by zach */
let fecha = Vue.prototype.$isServer ? {format: function() {}, parse: function() {}} : require('./date');
let fechaModel = fecha;
if (!Vue.prototype.$isServer && !fecha.format) {
fechaModel = window.fecha;
}
不进行 import
引用,而是改成 require 引用
(import 只能写在文件头)。
最后改成这样子就可以了。 如果是 node 环境,就直接给一个 空对象, 然后两个当下会用的 format
和 parse
的空函数,这个一定要设置,不然会报函数未定义的错误:
然后要判断如果不是 node 环境,这个 fecha
对象是否有 format
方法,如果没有这个方法的话,那么就要用 window.fecha
来代替。
其实这个逻辑就是用来 vuepress
本地运行的时候用的,因为那时候 require date
的时候,走的是 window
那一层,但是导出来是一个空对象,所以并没有 format
方法,所以才需要 window.fecha
对象来替代。但是跑 air-ui
本地项目的话,根本不会走到那边去,而是直接 export
导出来完整的 fecha
对象,不会走到那边去。
这样子,以下这种环境都可以打包成功:
vuepress
的 本地运行环境 和build
生成静态文件环境air-ui
的本地运行环境 和 生成dist
文件正式环境。
总结
到这一节结束,关于 air-ui
用 vuepress
写文档遇到的问题和需要注意的问题,基本上都描述清楚了。接下来我们讲一下 air-ui
的多语言机制,以及跟 element-ui
的多语言机制有啥不同。
系列文章:
自建vue组件 air-ui (1) -- 为啥我要自建一个类 element ui 的组件
自建vue组件 air-ui (2) -- 先分析一下 element ui 项目
自建vue组件 air-ui (3) -- css 开发规范
自建vue组件 air-ui (4) -- air-ui 环境搭建和目录结构
自建vue组件 air-ui (5) -- 创建第一个组件 Button
自建vue组件 air-ui (6) -- 创建内置服务组件
自建vue组件 air-ui (7) -- 创建指令组件
自建vue组件 air-ui (8) -- 实现部分引入组件
自建vue组件 air-ui (9) -- 用 vuepress 写文档
自建vue组件 air-ui (10) -- vuepress 写文档 (进阶版)
自建vue组件 air-ui (11) -- vuepress 写文档 (爬坑版)
自建vue组件 air-ui (12) -- 国际化机制
自建vue组件 air-ui (13) -- 国际化机制(进阶版)
自建vue组件 air-ui (14) -- 打包构建(dev 和 dist)
自建vue组件 air-ui (15) -- 主题定制
自建vue组件 air-ui (16) -- 打包构建 pub 任务
自建vue组件 air-ui (17) -- 开发爬坑篇以及总结