记一次 Next.js 样式异常的排查与 pr 修复 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kaiduo
V2EX    Node.js

记一次 Next.js 样式异常的排查与 pr 修复

  •  
  •   kaiduo 2022-11-02 23:15:52 +08:00 5013 次点击
    这是一个创建于 1082 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文地址: https://github.com/xiaoxiaojx/blog/issues/46

    问题简述

    开发同学反馈 Next.js 项目 next dev 命令启动后浏览器访问页面 css 样式有丢失,表现为 style 标签的内容不是 css 样式而是一个 url

    <style>/_next/static/media/globals.23a96686.css</style> 

    面对这个略显奇葩的问题该如何排查了 ?

    问题排查

    Step1 确认是否配置了错误的 loader

    • 分析: 给 css 文件设置错了 loader 造成结果不符合预期也说得过去
    • 结论: 没有发现可疑的 loader, 经过查看 webpackConfig 发现会命中红圈 oneOf 数组索引为 6 的规则, 即 css 文件会依次被 postcss-loader > css-loader > next-style-loader 来依次处理, 看上去没有任何问题

    Rule.oneOf : An array of Rules from which only the first matching Rule is used when the Rule matches.

    同时根据 Rule.oneOf 的定义确认了只会从 oneOf 数组中找到一个匹配到的规则就会停止, 那么 loader 应该是都被正确设置了

    Step2 处理 css 文件的 loader 有 bug ?

    • 分析: 确认了没有奇怪的 loader 掺杂进来, 那么是不是命中的 loader 有 bug 了
    • 结论: 果然有 bug

    loader 正常是按从下到上, 从右到左的顺序执行, 即如上配置会按从 postcss-loader > css-loader > next-style-loader 来依次处理。

    // packages/next/build/webpack/loaders/next-style-loader/index.js import path from 'path' import isEqualLocals from './runtime/isEqualLocals' import { stringifyRequest } from '../../stringify-request' const loaderApi = () => {} loaderApi.pitch = function loader(request) { // ... } module.exports = loaderApi 

    但是由于 next-style-loader 导出了 pitch 属性, loader 的顺序将会变成首先运行 pitch 函数, 相关文档见 Pitching Loader

    |- next-style-loader `pitch` |- requested module is picked up as a dependency |- postcss-loader normal execution |- css-loader normal execution |- next-style-loader normal execution 

    那么我们先从 next-style-loader 的 pitch 函数开始排查, 然后发现了如下语句

    // packages/next/build/webpack/loaders/next-style-loader/index.js var cOntent= require(${stringifyRequest(this, `!!${request}`)}); 

    上面语句补充上变量的值后相当于如下

    var cOntent= require('!!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css') 

    这里的依赖关系先简单理一下, 比如 _app.tsx 引用了 globals.css

    // pages/_app.tsx import '../styles/globals.css' 

    globals.css 模块的内容被 next-style-loader 处理后的内容类似于下面这样

    // styles/globals.css var cOntent= require('!!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css') const style = document.createElement('style'); style.appendChild(document.createTextNode(content)); document.head.append(style) 

    所以 style 标签里面的 content 的源头其实是 require 的 !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css 模块提供

    // !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css module.exports = ? 

    那么对于 !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css 这个路径上带有 loader 的模块其文件后缀依然为 .css, 那么不还得命中 oneOf 数组索引为 6 的规则么 ?

    经过 debug webpack 的代码发现结果是意外的 , 它命中的是 oneOf 数组索引为 8 的规则

    Rule.issuer: A Condition to match against the module that issued the request. In the following example, the issuer for the a.js request would be the path to the index.js file.

    关于 Rule.issuer: 比如 pages/_app.tsx 文件里面 import 或者 require 了 globals.css, 那么对于 globals.css 而言, 它的 issuer 为 pages/_app.tsx

    让我们回头查看一下为什么没有命中索引为 6 的规则, 发现索引为 6 规则非常重要的一点是要求引用该文件的 issuer 必须只能是 pages/_app.tsx, 见下图红圈的 issuer.and 字段

    而 !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css 模块其实是被 next-style-loader 代理后的 globals.css 所 require, 所以它的 issuer 竟然为 styles/globals.css

    至此 !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css 模块经过 postcss-loader > css-loader 处理后, 最后还要被 webpack 内置的文件类型 asset/resource 处理(相当于 file-loader ), 最终处理完成它的 module.exports 其实为如下

    // '!!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css' module.exports = "/_next/static/media/globals.23a96686.css" 

    所以被 next-style-loader 处理后的 styles/globals.css 模块最后 append 了一个 url 字符串到了 style 标签中造成了本次 css 样式的异常 !!!

    // styles/globals.css var cOntent= require('!!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css') const style = document.createElement('style'); style.appendChild(document.createTextNode(content)); document.head.append(style) 

    疑惑解答

    为什么 Rule.oneOf 貌似 pick 了多次规则?

    对于 styles/globals.css 模块而言只选择了一次即 oneOf 数组索引为 6 的规则, 而经过 next-style-loader 的代理修改后的 require 语句又产生了新的模块 !!xxx/css-loader/src/index.js??xxx/postcss-loader/src/index.js??ruleSet[1].rules[3].oneOf[6].use[2]!./globals.css, 于是新的模块又进行了一次规则 pick, 所以并不算违背了 Rule.oneOf 的定义

    即便新的模块又命中了 oneOf 数组索引为 6 的规则, 由于新的模块路径前缀已经包含了 postcss-loader 与 css-loader 也会因为如上图红圈的 if 条件判断不满足而不会被添加重复的 loader

    Next.js 的 oneOf 数组索引为 8 的规则真实意图是干什么的?

    根据如下 Next.js 对应的代码可知, Next.js 的预期是 issuer 为 *.css 的模块将要被 asset/resource (类似于 file-loader) 给处理, 比如 globals.css 有一行代码为 background-image: url("./xxx.png"), 那么此时 xxx.png 就要被 asset/resource 给处理

    // next/dist/build/webpack/config/blocks/css/index.js markRemovable({ // This should only be applied to CSS files issuer: regexLikeCss, // Exclude extensions that webpack handles by default exclude: [ /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/, /\.webpack\[[^\]]+\]$/, ], // `asset/resource` always emits a URL reference, where `asset` // might inline the asset as a data URI type: 'asset/resource' }), 

    问题解决

    Next.js 没想到被 next-style-loader 修改后还存在 .css 文件 require .css 文件的情况, .css 文件被 asset/resource 处理后返回一个 url 就导致了本次的 bug

    其实 .css 中 require 的图片、字体等文件被 asset/resource 处理是符合预期, 如果被 require 的是 .css 文件就得排除, 更多见 pr

     issuer: regexLikeCss, // Exclude extensions that webpack handles by default exclude: [ + /\.(css|sass|scss)$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/, 
    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     919 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 22:58 PVG 06:58 LAX 15:58 JFK 18:58
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86