vue|vuepress打包报错(error Error rendering /:)

问题 使用vuepress写文档网站,为了实现element-ui类似的组件预览效果,项目里面将ant-design-vue和基于ant-design-vue的自己封装的组件引入项目中,开发环境能正常运行。当运行Build打包后,报错:error Error rendering /:
vue|vuepress打包报错(error Error rendering /:)
文章图片

方案1 经查询vuepress github issuse 得到的答案是,vuepress是服务端渲染的,浏览器的 API 访问限制。在页面加载第三方组件的时候,可能出现找不到的情况,建议在当前页面使用时再引入。
vue|vuepress打包报错(error Error rendering /:)
文章图片

内容链接:https://v1.vuepress.vuejs.org/zh/guide/using-vue.html#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84-api-%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6
当然,如果不需要组件预览效果,及:::demo标志,就不会报错。需要该效果时,按照官网的做法,使用component标签,在需要的md 代码块里面动态加载组件,可以解决该问题

mounted(){ import("my-component").then(myComponent=>{ console.log("myComponent", myComponent) this.dynamicComponent = myComponent.Tree }) },

当然还有一种方法就是在mounted里面import组件并且注册组件,template部分照常使用之前的标签
> import Vue from "vue" export default { mounted(){ import("my-component").then(myComponent=>{ console.log("myComponent", myComponent) Vue.component("myTree",myComponent.Tree) }) } }

然而运行后,报错my-tree没有注册。Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the “name” option.
found in
方案2 【vue|vuepress打包报错(error Error rendering /:)】方案1,问题看是解决了,没有报错了,但是。这不是我们想要的。
本质需求是实现组件库的组件预览效果,并且能复制代码,方案1能预览,但是代码都变成了component标签,不符合需求。
接下来我们想排查下是什么组件影响了打包:
采用本地建测试仓库,本地建测试组件库,本地建测试文档项目,逐个移植原组件库的组件到测试组件库中,发布到测试仓库后,更新测试文档,然后执行本地预览和打包命令。最终找出影响打包的组件有dialog,uploadAvatar,preview,cropper-img等组件。这些组件的共同点就是都直接或间接用到了document操作dom,还有其他window的方法,或者bom的方法事件,或者在组件内注册第三方组件,如本项目中就用的atob,btoa,这个也是造成出现该错误的原因之一。经在upoadAvatar去掉document得到验证。目前除了这几个组件,其他均可以实现组件预览效果。但是这几个组件只要引入就报错,也影响了全局。
最终解决
知道了问题所在,结合上面的思路,总之的一点就是想要用window或bom方法事件,一定要在mounted后执行。这里来一个一个看。首先看dialog组件,之所以会报错就是dialog组件在created生命周期的时候用了document(下面只展示关键代码,多余的代码略)
import { VNode } from "vue" import { Component, Prop, Vue, Watch } from "vue-property-decorator" import { dialogConfig } from "./type"export default class Dialog extends Vue {created() { this.dialogInit() this.getWindowSize() }/** * 弹窗初始化 * @private */ private dialogInit():void { this.id = "lc-dialog" + (Math.random() * 1000000 + new Date().getTime()).toString(16).split(".")[0] this.$nextTick(() => { const dialogDom:any = document.getElementById(that.id) if (!dialogDom) { return } this.dialogDom = dialogDom this.headerDom = dialogDom.querySelector(".ant-modal-header") this.footerDom = dialogDom.querySelector(".ant-modal-footer") }) }/** * 获取窗口尺寸 * @private */ private getWindowSize():void { const that = this as any this.windowSize = { windowWidth: document.documentElement.clientWidth, windowHeight: document.documentElement.clientHeight } // // console.log(that.dialogScreen) // // console.log(that.isScreen)window.onresize = () => { if (this.dialogScreen && this.isScreen) { clearTimeout(this.debounceTimeOut) this.debounceTimeOut = setTimeout(() => { this.windowSize = { windowWidth: document.documentElement.clientWidth, windowHeight: document.documentElement.clientHeight } }, 300) } } } render (h:any):VNode { ... } }

那这里就很简单,将created改成mounted就行
再来看看图片裁剪组件,之前的代码是
> import Vue from "vue" import VueCropper from "vue-cropper" import { getZIndexByDiv } from "../../utils/lc-methods" Vue.use(VueCropper)export default { name: "CropperImg", ... }

这里需要解决的是vue-cropper组件的按需引入问题。按照vue-press的思路,可以使用动态import,于是开始尝试如下:
> import { getZIndexByDiv } from "../../utils/lc-methods"export default { name: "CropperImg", mounted() { import("vue-cropper").then(VueCropperModule => { this.component = VueCropperModule.VueCropper } }, ... }

然而运行后发现,没有生效。于是将import(“vue-cropper”).catch打印发现竟然报错了。vuepress是走node服务渲染模式,也可能和nodejs 服务端渲染不支持import有关,于是改成require引入,如下
> import { getZIndexByDiv } from "../../utils/lc-methods"export default { name: "CropperImg", mounted() { const VueCropperModule = require("vue-cropper") // console.log("VueCropperModule", VueCropperModule) this.component = VueCropperModule.VueCropper }, ... }

结果成功,引入该组件后,也不会再报错error Error rendering /:
然后看看头像上传组件,这个也比较坑。这个组件mounted没有document,也没引入第三方组件。无奈,只有使用注释大法逐步查找,最后确定是组件内部引入压缩图片的方法造成
import { compressImageFun } from "../../utils/img-method"

发现只要组件引入了该方法,不管是否使用,都会报错,而注释该方法就万事大吉。
于是取查找compressImageFun相关,开始因为无头绪,同样只能使用注释大法,最后查到罪魁祸首是atob,btoa函数。这样那么解决办法和引入第三方组件类似。compressImageFun里面不需要动,改引入的地方就行,如下
export default { name: "UploadAvatar", data () { return { compressImageFun: null, // 压缩函数注意这里因为无法直接使用compressImageFun,所以需要存在data中使用 } }, mounted() { const imgMethod = require("../../utils/img-method") this.compressImageFun = imgMethod.compressImageFun }, methods: {/** * 上传之前 * @param file * @returns {boolean} */ async beforeUpload (file) { return new Promise(async (resolve, reject) => { ...省略若干代码 if (!this.noCompress) { newCompressFile = await this.compressImageFun({ file: cropperFile || file }) cropperFile = newCompressFile.data } ... }) }, }

至此,该错误被攻克!!!

    推荐阅读