「踩坑记录」 Nuxt.js & Ant Design Vue 配置

Nuxt.js & Ant Design Vue 踩坑记录

最近开发一个带SEO以及部分后台功能的项目,Nuxt.js作为vue ssr框架可以非常好的完成这个需求,这里我选择了Ant Design Vue作为UI组件库。

以下是踩坑的一些记录 :

首先介绍一下项目情况和需求:

  • Nuxt.js是一个 Vue.js 通用框架,预设了使用 Vue.js 开发服务端渲染的各种配置。项目中使用的是Universal (SSR) 模式,因此首屏是由服务端完成渲染
  • 项目仅用到少数的UI组件,因此我们希望使用按需加载组件,以减少网络开销。

1. 按需加载引入组件

项目通过npx create-nuxt-app <项目名>创建,按需求选择这次使用的UI ant-design-vue。默认会为我们创建 nuxt.config.js配置项和plugins/antd-ui.js

首先我们移除掉 nuxt.config.js

{
   css: ['ant-design-vue/dist/antd.css'], 
   // 这里会全局引入所有样式,不是我们所希望的按需加载
}

参考官方文档antd-vue # 按需加载

// 同时参考 Nuxt-cli 创建项目时 创建的 plugin/antd-ui.js
import Vue from 'vue'
import Button from 'ant-design-vue/lib/button'; 
import 'ant-design-vue/lib/button/style'; // 或者 ant-design-vue/lib/button/style/css 加载 css 文件

Vue.use(Button.name, Button)

编译未通过,提示错误:

// https://github.com/ant-design/ant-motion/issues/44
.bezierEasingMixin();
^
Inline JavaScript is not enabled. Is it set in your options?

根据报错所提供的信息,less升级到3.0 后,需要 less-loader配置一个功能,才能正常使用:

// nuxt.config.js
{
  build: {
    loaders: {
      less: {
        javascriptEnabled: true
      }
    }
  }
}

编译通过,打开页面,返回500,提示:

image.png

编译能通过,说明代码编译没有问题,但是server端执行时出现了错误。尝试注释掉plugin/antd-ui.js引入样式文件这行 import 'ant-design-vue/lib/button/style'; ,server首屏渲染能够正常启动,单由于未引入的button组件样式,button不能按预期渲染。

打开 ant-design-vue/lib/button/style 目录查看:

// ant-design-vue/lib/button/style/index.js
'use strict';
require('../../style/index.less');
require('./index.less');

// ant-design-vue/lib/button/style/css.js
'use strict';
require('../../style/index.css');
require('./index.css');

// ant-design-vue/es/button/style/index.js
import '../../style/index.less';
import './index.less';

// ant-design-vue/es/button/style/css.js
import '../../style/index.css';
import './index.css';

可以看到,antd-vue将样式文件通过js引入,分别提供了less(定制主题)/css两种样式引入,且提供了两种模块格式的代码。

补充知识:现在的webpack@4+ 支持识别项目 packge.json module字段,使用ESModule的依赖更好的支持构建中的tree-shaking。

尝试分别引入4个样式的入口的js文件,编译通过,但会得到三种500报错:

import 'ant-design-vue/es/button/style/css.js'
import 'ant-design-vue/es/button/style/index.js'
// Cannot use import statement outside a module

import 'ant-design-vue/lib/button/style/index.js'
// Invalid or unexpected token

import 'ant-design-vue/lib/button/style/css.js'
// Unexpected token '{'

直接引入 css/less文件,编译通过,服务端不再报错,正常渲染:

import 'ant-design-vue/lib/button/style/index.less'
// or
import 'ant-design-vue/lib/button/style/index.css'

最后我们尝试,不引入样式,修改组件引入 es/button/...,编译通过,但仍抛出500服务端错误Cannot use import statement outside a module

image.png

根据前面的测试,猜测服务端侧渲染页面时出现了语法出错:

  1. Nuxt.js默认情况下,没有将node_modules引入的依赖正确编译到server侧,依赖内包含了只能被webpack识别的 import 'xxx.css' 或,require('xxx.less'),导致server代码执行时错误。
  2. Nuxt.js默认情况下,没有将ESModule语法编译到server侧。

接着测试:

// .nuxt.config.js
{
  plugins: [
    {
      src: '@/plugins/antd-ui',
      mode: 'client' // 仅 客户端使用plugin
    }
  ]
}

页面正常加载,样式渲染成功,确认了之前的猜测,但Vue.js 给出了错误提示:

image.png

搜索Nuxt官方文档

如果要使用Babel与特定的依赖关系进行转换,你可以在build.transpile中添加它们,transpile中的选项可以是字符串或正则表达式对象,用于匹配依赖项文件名。

移除plugin的mode: 'client',添加配置:

{ // nuxt.config.js
  build: {
    transpile: [/ant-design-vue/]
  }
}

终于,服务端渲染正常,样式也正常加载。

根据前面的尝试结果,我们可以考虑为client/server,添加不同的plugin用于引入组件,或者使用transplie选项将node_modules 引入的组件,纳入babel编译。这里是一个取舍问题:

  1. ui库提供了es语法模块更好的支持tree shaking,但我们又需要CommonJS模块语法的依赖来支持Node.js
  2. 如果将库二次babel编译,库可能已经经过了babel编译,可能同样会导致依赖引入变大。

其次,transpile选项应该不止包括babel,而是将node_modules引入的依赖都纳入到编译范围,对于引入其他模块类型文件(import 'xxxx.less')也做了处理(通过webpack),否则引入样式文件都会导致server端执行过程报错。

2. 引入babel-plugin-import

接着引入 ant-design-vue官方文档上使用的插件babel-plugin-import

// nuxt.config.js
{
  build: {
    babel: {
      plugins: [
        [
          'import',
          {
            libraryName: 'ant-design-vue',
            libraryDirectory: 'es', 
            // 选择子目录 例如 es 表示 ant-design-vue/es/component
            // lib 表示 ant-design-vue/lib/component
            
            style: true 
            // 默认不使用该选项,即不导入样式 , 注意 ant-design-vue 使用 js 文件引入样式
            // true 表示 import  'ant-design-vue/es/component/style' 
            // 'css' 表示 import 'ant-design-vue/es/component/style/css' 
          }
        ]
      ]
    }
  }
}

// plugins/antd-ui.js 
import Vue from 'vue'
import { Button } from 'ant-dsign-vue'
// 这时,可以通过 简写的方式引入样式和组件

Vue.use(Button)

3. pages目录内引入组件

flash.gif

上图是仅在pages/index.vue内引入组件,npm run build && npm run start 编译后的线上环境,可以看到样式在刷新首屏时,会看到闪烁的现象。这里出现的问题,原因在于入口文件并不包含页面内依赖的chunk,而是按需加载的,从编译结果上也可以看到 610kb 的包对应的chunk vendors.pages/index ,并不在entrypoint内。

build result

Nuxt.js默认设置下,生产环境的style是通过style-loader设置到head上的([nuxt-css-flash](https://zh.nuxtjs.org/faq/css-flash)),而不是单独打包出来。Nuxt.js` 路由页面由于是dynamic import,经过测试,即使css单独打包后,页面内引入的依赖,依然会有按需加载的问题。

因此,如果组件的被大多数页面使用,推荐在 plugin内注册组件或样式,或者通过 nuxt.config.js的css全局配置,直接引入对应的组件样式,同时关闭babel-plugin-importstyle选项,引入组件只做引入组件的工作。 (吐槽一句,iView只能引入单一全局样式,不能按需加载组件样式,1.5m的大小非常恐怖)

除此以外,webpack@4chunkSplitoptimization配置,Nuxt.js中可参考optimization,实现自己的需求。

4. 解决antd-icon 过大(传统艺能

前面生产环境的包可以发现,只使用了一个Button,却打包了600kb的依赖,检查后发现是引入了@ant-design/icon 包。

参考: https://github.com/HeskeyBaozi/reduce-antd-icons-bundle-demo

{ // nuxt.config.js
  build: {
    extend(config, ctx) {
      config.resolve.alias['@ant-design/icons/lib/dist$'] = path.resolve(__dirname, './assets/icon/antd-icon.js') // 引入需要的
    }
  }
}

4. 其他

在查找前面问题的解决办法过程中,antd偏右大佬回复了这么一句

https://github.com/ant-design/babel-plugin-import/issues/347#issuecomment-567405815

tree shaking主要依赖于依赖提供的源码是ES模块语法。antd项目通过提供模块语法依赖,以及将 style的引入放到js文件当中,实现组件的单独拆分,最大化按需引入,节省依赖大小。个人觉得使用babel-plugin-import最大的好处是节省按需引入组件的工序,无需每次都引入组件且引入组件的样式,如果可以接受更多的依赖引入语句,确实没有必须使用babel-plugin-import的必要。

// babel-plugin-import 作用
import { Button } from 'xxx'
// 编译为
import Button from 'xxx/lib/button' // lib 可选 es
import 'xxx/lib/button/style' // 可选,style选项,可选子目录

reference

https://github.com/vueComponent/ant-design-vue/issues/234
https://www.zhihu.com/question/265227812

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342