You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
相信大家在使用nextjs的时候,难免遇到一些坑。其实可能大部分原因在于 nextjs 做了很多封装,我们可能不能第一时间搞清楚包括它相关的所有配置,比如其中的webpack配置。我前面也写过 SSR 实现的文章和简单的轮子《实现ssr服务端渲染》,也知道 SSR 要实现为 nextjs 这样的三方框架,还是会需要经历很复杂编码的。
// Add a rule to include and parse all modules
config.module.rules.push({
test: /\\.+(js|jsx|ts|tsx)$/,
loader: options.defaultLoaders.babel,
include: includes
});
完全没有问题,可以正常使用。只是 antd-mobile 的 class 名称也被 cssModules 给改了,但是组件 dom 中的 class 名称并没有被修改,这样样式就不起作用了。ok,没有问题,这个简单,我们使用 css-loader api 中的 options.getLocalIdent,来控制修改 class 名称。代码大致如下
开发环境页面 A 切换到 B 后,B 没有样式。这个情况是在开发模式下才有。
比如我初次启动应用之后,访问 A,A 发现没登录访 B,这个时候 B 样式加载不出来,页面没样式。如果我在 B 页面刷新一次,让服务端渲染一次,然后 A 再跳到 B 就有样式了。我发现在第一次从 A 跳到 B 的时候,有一个类似这样的一个请求:/_next/static/chunks/styles.js?ts=1557217006063,就是 B 样式的热更新文件。但是实际 _next/static/css/styles.chunk.css 这个文件里没有成功载入 B 的样式。而当我们用服务端渲染一次 B 页面,也就是在 B 的路由下刷新一次。而后的 chunk.css 就有样式了。
我们再看看生产环境,生产环境,nextjs 会把所有依赖的 css 打包到一个 chunk.css 文件中,在首次渲染的时候,整个应用的所有样式都已经被载入了,比如 A 和 B 的样式都有了。所以在切换页面的时候,样式都没问题。
依照这个情况看来,开发环境下,样式是被加载到运行时的内存中的,一旦有用服务端渲染 A 页面,A 的样式就会被添加进服务端内存中,再用服务端渲染一次 B 页面,而后请求 chunk.css 就才会有两个页面的样式。问题在于开发环境下的热更新没有起到作用,应该是一个官方的bug。
相信大家在使用nextjs的时候,难免遇到一些坑。其实可能大部分原因在于 nextjs 做了很多封装,我们可能不能第一时间搞清楚包括它相关的所有配置,比如其中的webpack配置。我前面也写过 SSR 实现的文章和简单的轮子《实现ssr服务端渲染》,也知道 SSR 要实现为 nextjs 这样的三方框架,还是会需要经历很复杂编码的。
总归有时候遇到问题,在网上也查不到一个正确的解决方案。比如,我为此头痛几天的 antd-mobile 按需加载,最开始我无法正常使用,就只能全局引入 antd-mobile的min.css,这导致我要在页面加载 164k 的 css 文件,我们使用 nextjs 就是为了提升加载速度,这种情况不能忍啊!
言归正传,先说说我遇到的问题,我使用了 antd-mobile 并且需要对它进行按需加载,下面是官方的文档,推荐我们使用 babel-plugin-import。
按照 nextjs 官方文档我们在 .babelrc 中添加插件
可当我运行的时候报错了,报错如下。最开始感到奇怪,我并没有引入这个包,后来发现其实是 antd-mobile 中有引入它。但是为什么会报错呢, 便想到应该是 webpack loader 的问题,我认为是 loader 排除了node_modules。(这里解释一下,node_modules 中的包本应该都是打包好的,但是一些情况下,我们是直接引入源代码中的模块的,那这样的话我们就需要让我们的 loader 解析 node_modules 中的代码,然而有些默认配置就是排除了 node_modules 的,这样就会导致无法解析)。
然后我在 next.config.js 中,定义 webpack 方法,打印出 webpack 配置。 nextjs 中的 webpack 配置大致是引入了一个 next-babel-loader 这样的 loader,而我们使用next-css、next-less或者next-sass等插件,相关的 loader 会被 push 到 rules 中。 核心的loader 就是 next-babel-loader。然而我在其参数中并没有发现 exclude, 到是有 include,而后我往 include 里添加 node_modules 下需要的组件正则,发现并没有效果。而后我经历了各种痛苦,尝试过各种方面的办法,网上也查不出解决方案。好,跳过心酸的部分。
再后来我开始仔细的一个个看官方的插件,我找到了它:next-transpile-modules,从名称上来看似乎和我想要的有点关系。https://github.com/martpie/next-transpile-modules。
一看文档果然,它就是我要找的,它就是解决 node_modules 中代码不被 loader 解析的问题。我使用了它,这时报错信息变了(其实后来我弄比较清楚以后就没有报错了,可能当时配置改的比较多,哪里影响到了),我觉得似乎起到作用了,但是还是会报错。于是我便看了一下它的代码,我终于发现了 webpack.externals 这个配置,原来是这个地方排除了解析外部依赖。如果我们使用插件 transpile 并配置好 transpileModules: ["antd-mobile"],transpile 内部会生成 includes 正则,在 externals 执行时,会排除掉我们配置的 node_modules 模块,因此 antd-mobile 就能被正常解析了,代码如下
而后它又添加了一个 next-babel-loader 到 rules 中,现在其实有两个 next-babel-loader 在 webpack 配置中。我认为这个配置是多余的,并且就是之前我可能哪里没配置对,这个多余的 loader 让我编译报错了,我把它生成的多余 loader 删除才没有报错的。
最后在我完全能正常运行的时候,还是尝试删除了它,发现并没有报错,因为从理论上来说,这个重复的loader本身也没有用,因此我给作者提了一个建议,建议去掉这个新loader, 对方说再认真看看。这里:martpie/next-transpile-modules#32。(事实证明我理解错了,请看文章后文详情)
我当前使用的 next 是8.x,在6.x里,我看了下它确实是用的 exclude 来排除的 node_modules,到 8 以后改为 externals 了,一定有它官方的道理吧。如果你用的是6.x,你可以尝试修改 exclude,不过建议大家都升级为 8 吧,很平滑的。
第二个问题,可能也是大家比较常见的,那就是 cssModules。官方代码是这样的
完全没有问题,可以正常使用。只是 antd-mobile 的 class 名称也被 cssModules 给改了,但是组件 dom 中的 class 名称并没有被修改,这样样式就不起作用了。ok,没有问题,这个简单,我们使用 css-loader api 中的 options.getLocalIdent,来控制修改 class 名称。代码大致如下
通过阅读 css-loader 源码,发现其内部运行过程,它内部有一个 css-loader/lib/getLocalIdent.js 方法,如果用户自定义了 getLocalIdent 方法,它在编译 cssmodules 时,便会用用户定义的方法,否则使用自带的方法。我的想法就是通过自定义 getLocalIdent, 正则判断 node_modules,也就是当前样式如果是来自于 node_modules 中文件的话,我返回它本身的名称,就是不改动它,而它是我们的源码的话,我执行 css-loader 本身的 getLocalIdent 方法。这样就既使我们自己的代码能被 cssmodules,而三方库的代码不被 cssmodules 影响。
最后附上两个配置文件 .babelrc 、 next-config.js 和 postcss.config.js
pxtorem是转换px为rem,有的需要的自取,如果此方案解决了你的问题,点个赞吧~
注意:
如果还会存在 antd 的报错,在 next.config.js 中添加 webpack 配置方法去掉 next-transpile-modules 额外添加的 loader,清空其 include。 这个多余的 loader 确实会导致 bug,或许你在使用的时候此包的代码已经更新。
终解:
后来我终于想清楚了,首先 next-transpile-modules 的目的就是让 node_modules 中的包可以使用 next-babel-loader ,�它的文档第一句就是这个意思,我当时理解错误了。
其次我们再来说说 webpack.externals 这个配置,比如 nextjs 默认就是如下这样配置的,它把 node_modules 下的 js 作为一个公共的js来处理,当这样配置以后,webpack 就不会去分析 node_modules 下的 js 的依赖了。
比如我自己在 node_modules 里写一个文件夹 @test,里面是一个 index.js,index.js require了同级的 b.js,然后我们在 nextjs 的项目代码里引入 @test/index.js ,编译时就会报错,报错的行就在 require('b.js') 这里。
再来说说 next-transpile-modules, 它做了两个事情,第一是从 nextjs 默认的 externals 中,排除掉我们定义的 transpileModules: ["antd-mobile"],这样 antd-mobile 中的 js 就会被 webpack 正常解析依赖了。而后新建了一个 next-babel-loader ,include 的值是 transpileModules 配置的 ["antd-mobile"]。 由于我们的 antd-mobile 中的代码不需要被 next-babel-loader 解析,甚至如果使用 next-babel-loader 解析就会报错,因此我前面的配置把它添加的 loader 的 include 给清空了,这样所有的配置就 ok 了。因此我们只需要它其中的 externals 功能,ok,next.config.js 最终代码如下 ( .babelrc 和 postcss.config.js 参照上面不变)
发现的一些问题记录
1.页面切换样式问题
开发环境页面 A 切换到 B 后,B 没有样式。这个情况是在开发模式下才有。
比如我初次启动应用之后,访问 A,A 发现没登录访 B,这个时候 B 样式加载不出来,页面没样式。如果我在 B 页面刷新一次,让服务端渲染一次,然后 A 再跳到 B 就有样式了。我发现在第一次从 A 跳到 B 的时候,有一个类似这样的一个请求:/_next/static/chunks/styles.js?ts=1557217006063,就是 B 样式的热更新文件。但是实际 _next/static/css/styles.chunk.css 这个文件里没有成功载入 B 的样式。而当我们用服务端渲染一次 B 页面,也就是在 B 的路由下刷新一次。而后的 chunk.css 就有样式了。
我们再看看生产环境,生产环境,nextjs 会把所有依赖的 css 打包到一个 chunk.css 文件中,在首次渲染的时候,整个应用的所有样式都已经被载入了,比如 A 和 B 的样式都有了。所以在切换页面的时候,样式都没问题。
依照这个情况看来,开发环境下,样式是被加载到运行时的内存中的,一旦有用服务端渲染 A 页面,A 的样式就会被添加进服务端内存中,再用服务端渲染一次 B 页面,而后请求 chunk.css 就才会有两个页面的样式。问题在于开发环境下的热更新没有起到作用,应该是一个官方的bug。
此 issue 说不是 next 核心的 bug,是三方插件的问题,那么问题应该在next-css, vercel/next.js#4732 。
The text was updated successfully, but these errors were encountered: