最近开发一个带SEO以及部分后台功能的项目,Nuxt.js
作为vue ssr框架可以非常好的完成这个需求,这里我选择了Ant Design Vue
作为UI组件库。
以下是踩坑的一些记录 :
首先介绍一下项目情况和需求:
Nuxt.js
是一个 Vue.js 通用框架,预设了使用 Vue.js
开发服务端渲染的各种配置。项目中使用的是Universal
(SSR) 模式,因此首屏是由服务端完成渲染。项目通过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
根据前面的测试,猜测服务端侧渲染页面时出现了语法出错:
Nuxt.js
默认情况下,没有将node_modules
引入的依赖正确编译到server侧,依赖内包含了只能被webpack
识别的 import 'xxx.css'
或,require('xxx.less')
,导致server代码执行时错误。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
编译。这里是一个取舍问题:
tree shaking
,但我们又需要CommonJS
模块语法的依赖来支持Node.js
。其次,transpile
选项应该不止包括babel
,而是将node_modules
引入的依赖都纳入到编译范围,对于引入其他模块类型文件(import 'xxxx.less'
)也做了处理(通过webpack
),否则引入样式文件都会导致server端执行过程报错。
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)
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-import
的 style
选项,引入组件只做引入组件的工作。 (吐槽一句,iView
只能引入单一全局样式,不能按需加载组件样式,1.5m的大小非常恐怖)
除此以外,webpack@4
的chunkSplit
由optimization
配置,Nuxt.js
中可参考optimization
,实现自己的需求。
antd-icon
过大(传统艺能前面生产环境的包可以发现,只使用了一个Button
,却打包了600kb的依赖,检查后发现是引入了@ant-design/icon
包。
参考: https://github.com/HeskeyBaozi/reduce-antd-icons-bundle-demo
首先在文件当中引入Icon组件
然后在plugins文件夹下创建icons.js,引入需要的图标
// icons.js
export {
// 需要使用到的 Icons
HeartOutline,
MehOutline,
SmileOutline,
DownOutline,
CloseOutline,
StarOutline
} from '@ant-design/icons';
最后把icons.js引入到nuxt配置文件当中
{ // nuxt.config.js
build: {
extend(config, ctx) {
config.resolve.alias['@ant-design/icons/lib/dist$'] = path.resolve(__dirname, './plugins/icons.js') // 引入需要的
}
}
}
最后主要一点:引入的图标必须显示的在界面中呈现,否则图标加载不出来
例如评分的星星图标
在查找前面问题的解决办法过程中,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选项,可选子目录
https://github.com/vueComponent/ant-design-vue/issues/234
https://www.zhihu.com/question/265227812