关于webpack:利用-statsjson-定位-nrwlreact-webpack-配置问题

44次阅读

共计 28889 个字符,预计需要花费 73 分钟才能阅读完成。

背景

团队应用 NX 这一 monorepo 工具来搭建 React 利用。NX 基于 React 利用在 webpack 打包时增加了 url-loader 的相干配置。然而共事反馈该 url-loader 针对局部援用的图片文件不起作用。

定位

url-loader 作用

url-loader,简而言之,能够将利用中援用到的一些资源文件(例如图片)转换成 base64 的数据格式,而后嵌入到咱们的利用中(例如 HTML 的 img src, css 中的 url 函数),这样便无需针对该资源发动网络申请,节俭申请资源。

以下是 url-loader 的配置举例:

{"test": "/\\.(png|jpe?g|gif|webp)$/",
  "loader": require.resolve("url-loader"),
  "options": {
    "limit": 10000, // 10kB
    "name": "[name].[hash:7].[ext]"
  }
}

上述配置的意思就是针对 10kB 以下大小的一些常见图片格式文件应用 url-loader 解决,否则应用 fallback loader 解决,url-loader默认的 fallback loader 是file-loader。超过大小的文件会被其解决,相应的 options 会传给file-loader,例如 name,最终解决后的文件名会包含 7 位 hash。

具体针对哪局部文件不起作用?

因为 NX 并不是写好 webpack 配置,再应用 webpack 指令进行打包。而是以 Angular CLI builder 的模式,引入 webpack 并进行代码编写,领有高度定制化,因而一开始并没有细看其 builder 的源码,很难找到具体是什么文件不起作用,而哪局部又没有问题。

一开始共事与之前的一个利用比照,发现其打包的一部分图片产物被 file-loader 解决,没有最终文件;而一部分则有最终文件,然而 hash 位数为默认的 20 位而不是 file-loader 解决后的 7 位;最初一部分文件则位数正确。

通过一些比对后发现,同一个图片文件,如果在款式文件(例如.scss)中援用则都会生成 20 位 hash 的文件名,而在 j(t)sx 中则配置失效。

查看 webpack config,尤其是款式文件那局部

找到对应的文件后,便须要查找是哪个 loader 解决了该文件,最先想到的是间接输入对应的 config.module 配置,因为 NX React 利用应用的配置文件为@nrwl/react/plugins/webpack.js,编辑该文件,减少如下局部:

PS: 因为正则表达式 JSON 没有对应的表达形式,因而 loader 的 test 局部只会是一个 {},不便于确认文件类型,因而能够应用其toString() 办法作为 JSON.stringfy() 时的处理函数,以此直观显示匹配的文件类型。

const fs = require('fs');

RegExp.prototype.toJSON = RegExp.prototype.toString;

function getWebpackConfig(config) {
  // ...
  fs.writeFile(
    './webpack-config.json',
    JSON.stringify(config.module),
    null,
    () => {}
  );
  return config;
}

module.exports = getWebpackConfig;

打印进去的 config 如下(仅提取匹配局部):

{
  "rules": [
    {
      "test": "/\\.css$|\\.scss$|\\.sass$|\\.less$|\\.styl$/",
      "oneOf": [
        {
          "exclude": [
            "/Users/tianzhi/dev/nx-examples/libs/shared/styles/src/index.scss",
            "/Users/tianzhi/dev/nx-examples/libs/shared/header/index.scss",
            "/Users/tianzhi/dev/nx-examples/node_modules/normalize.css/normalize.css"
          ],
          "test": "/\\.scss$|\\.sass$/",
          "use": [
            {"loader": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js"},
            {
              "loader": "/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js",
              "options": {"ident": "embedded", "sourceMap": false}
            },
            {
              "loader": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js",
              "options": {
                "implementation": {"info": "dart-sass\t1.26.10\t(Sass Compiler)\t[Dart]\ndart2js\t2.8.4\t(Dart Compiler)\t[Dart]",
                  "types": {},
                  "NULL": {},
                  "TRUE": {"value": true},
                  "FALSE": {"value": false}
                },
                "sourceMap": false,
                "sassOptions": {"precision": 8, "includePaths": [] }
              }
            }
          ]
        }
      ]
    },
    {"test": "/\\.(png|jpe?g|gif|webp)$/",
      "loader": "/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js",
      "options": {"limit": 10000, "name": "[name].[hash:7].[ext]" }
    },
    {
      "test": "/\\.svg$/",
      "oneOf": [
        {"issuer": { "test": "/\\.[jt]sx?$/" },
          "use": ["@svgr/webpack?-svgo,+titleProp,+ref![path]",
            {
              "loader": "/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js",
              "options": {
                "limit": 10000,
                "name": "[name].[hash:7].[ext]",
                "esModule": false
              }
            }
          ]
        },
        {
          "use": [
            {
              "loader": "/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js",
              "options": {"limit": 10000, "name": "[name].[hash:7].[ext]" }
            }
          ]
        }
      ]
    }
  ]
}

能够看到针对.scss 文件解决的 loader 及程序为:sass-loader -> postcss-loader -> style-loader

最纳闷的中央也就在这,个别状况下,咱们应用了 postcss-loader 后还会应用 css-loader 进行解决,css-loader会解析 @import 以及 url() 语法,将其转化为 import/require(),这样一来便能交给其余 loader 例如url-loader 进行解决。而对于postcss-loader,是因为它的 autoprefixer 十分闻名。

所以到此为止还是无奈确认具体是哪个 loader 解决了 url() 语法以至于 url-loader 无奈进行解决。然而能够确定的是答案就在这三个 loader 之一,因为这个时候还不理解 postcss-loader 的插件机制,我甚至狐疑是 sass-loader 的某个配置使得 url() 语法被提前解析。然而猜想始终不是方法,须要找到一个更确定的办法,可能晓得对应文件在 loader 解决前后的产物文件。

应用 stats.json 定位问题

如果能 debugging webpack 打包这一过程就好了,查找官网,还真发现有针对 webpack 打包过程的 debug 办法,有两种计划,我选用了较为简单的日志计划:查看数据报告 stats.json 文件

webpack 应用 --json 指令能够输入对应文件,在 NX 中则是--statsJson

输入后的日志报告条目很多,对于更多条目能够参考官网介绍,这里只须要搜寻对应文件,例如我这里是app.scss,会失去如下信息(仅提取无效信息):

{
  "chunks": [
    {"names": ["main"],
      "files": ["main.48c162c6863d37cba541.es5.js"],
      "hash": "48c162c6863d37cba541",
      "modules": [
        {
          "id": "/CXp",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "name": "./app/app.scss",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
          "issuerId": null,
          "issuerName": "./app/app.tsx",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            }
          ],
          "source": "var content = require(\"!!../../../../node_modules/postcss-loader/src/index.js??embedded!../../../../node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!./app.scss\");\n\nif (typeof content ==='string') {\n  content = [[module.id, content,'']];\n}\n\nvar options = {}\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = require(\"!../../../../node_modules/@nrwl/web/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\")(content, options);\n\nif (content.locals) {\n  module.exports = content.locals;\n}\n"
        },
        {
          "id": "GAnJ",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "/CXp",
          "issuerName": "./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            }
          ],
          "source": "..."
        },
        {
          "id": null,
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
          "name": "./app/app.tsx",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
          "issuerId": null,
          "issuerName": "./main.tsx",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            }
          ],
          "source": "... import'./app.scss';\n ..."
        },
        {
          "id": "aZ7I",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!./app/app.scss",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "/CXp",
          "issuerName": "./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            }
          ],
          "assets": [
            "banner2.50acf29cb9b5f2021724.png",
            "arrow-right.d9910f497ca75d78bcee.svg",
            "add-big@2x.ebff90ff08575204ca08.png"
          ],
          "source": "module.exports = \".image-png{background-image:url('add-big@2x.ebff90ff08575204ca08.png')}.image-svg{background-image:url('arrow-right.d9910f497ca75d78bcee.svg')}.image-big-png{background-image:url('banner2.50acf29cb9b5f2021724.png')}\""
        }
      ]
    }
  ]
}

先来说几个条目概念:

  1. chunks是咱们这次打包的 chunk 列表,每个 chunk 里的 modules 对应组成该 chunk 的 module 列表
  2. identifier为模块外部惟一标识
  3. issuer为该模块的援用起源
  4. source为模块的 stringfy 后的源码
  5. assets为该模块蕴含的动态资源

按照issuer,咱们能够失去调用程序为:

  • app.tsx

    • /CXp(style-loader!postcss-loader!sass-loader!app.scss)

      • GAnJ(style-loader/injectStylesIntoStyleTag.js)
      • aZ7I(postcss-loader!sass-loader!app.scss)

能够了解为:app.tsximport './app.scss' 被匹配并解析为外部模块/CXp,该模块源码sourceformat 后为:

var content = require('!!../../../../node_modules/postcss-loader/src/index.js??embedded!../../../../node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!./app.scss');

if (typeof content === 'string') {content = [[module.id, content, '']];
}

var options = {};

options.insert = 'head';
options.singleton = false;

var update = require('!../../../../node_modules/@nrwl/web/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js')(
  content,
  options
);

if (content.locals) {module.exports = content.locals;}

不难发现,该模块又援用了外部模块 aZ7I 和第三方 style-loader 的模块GAnJ

第三方 style-loader 的模块 GAnJ,也就是injectStylesIntoStyleTag.js 的作用正如名字所述,会将最终款式产物插入到 HTML 的 style 标签。咱们只须要持续剖析生成的外部模块 aZ7I,其source 如下:

module.exports =
  ".image-png{background-image:url('add-big@2x.ebff90ff08575204ca08.png')}.image-svg{background-image:url('arrow-right.d9910f497ca75d78bcee.svg')}.image-big-png{background-image:url('banner2.50acf29cb9b5f2021724.png')}";

没有再次援用,仅为最终款式的字符串,将其提取为 CSS:

.image-png {background-image: url('add-big@2x.ebff90ff08575204ca08.png');
}
.image-svg {background-image: url('arrow-right.d9910f497ca75d78bcee.svg');
}
.image-big-png {background-image: url('banner2.50acf29cb9b5f2021724.png');
}

就是源码中的三个 url() 调用,然而外面的门路曾经被解析,援用文件名蕴含 webpack 默认的 20 位 hash 而不是 url-loader 配置中的 7 位,而且前两个图片文件大小均小于 10KB,本应该被 url-loader 转换为 base64 格局。

看到这里,其实仅仅晓得 url() 被通过模块 aZ7I 后被更改,aZ7I的标识字段为/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??embedded!/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js??ref--5-oneOf-3-2!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss

将标识以 ! 分隔后,失去 .../postcss-loader/src/index.js??embedded.../sass-loader/dist/cjs.js??ref--5-oneOf-3-2 以及 app.scss,从标识符中咱们能够晓得 webpack 解决 app.scss 文件应用的 loader 及程序,即通过 sass-loader 解决后再由 postcss-loader 解决造成了上述产物。

这里我有一个疑难:

为什么 webpack 没有把过程持续拆分,也就是把上述模块再细化成两个模块,多出的一个模块则独自为 sass-loader 解决后的产物。如果可能细分,则能晓得具体是哪个 loader 解决了url()

我临时没找到这个问题的答案,所以持续查找。

先是去搜寻 sass-loader,发现其并没有解决url();而后是postcss-loader,发现了其反对第三方插件,在这之前我还提了一个 issue,通过逐渐寻找,发现postcss-url 插件 会解决url(),然而我在 NX 的依赖中却没找到它。

晓得插件机制后,我同时也开始浏览 NX 源码的 style loader 配置局部,终于发现原来是它们本人创立了一个插件用来解决 @importurl(),相当于代替了 css-loader。然而针对 react 的 webpack 配置中引入url-laoder 时却没有思考这部分,导致图片等文件无奈再被 url-loader 解决。

解决方案

只管这个问题的影响没有那么大,然而肯定要防止对同一个大于 10KB 的图片文件同时在款式文件和 j(t)sx 文件中援用,这样会导致同一个文件产生两份产出,一个文件名 7 位 hash,另一个则是 20 位 hash,这会导致针对同一个文件发动两次网络申请,如果文件较多将会节约很多资源。而且这的确也会让人费解,因而我提出了一个 issue 用于追踪。

然而鉴于 NX 对于 style 局部的 webpack 配置是集成在定制化的 webpack builder 代码文件中,而 url-loader 等配置却是 react 独自的,预计 NX 也不好批改这部分。

给团队的长期解决方案是:

  1. 如果想应用 url-loader 解决所有图片文件,尽量应用 <img /> 标签而不是 url() 来引入图片
  2. 如果并不一定要应用 url-loader 解决,能够应用绝对路径前缀例如 /assets/xxx 援用,同时配置 assets 选项,集体认为,这才是 assets 配置的正确应用形式
  3. 最初还是肯定要防止对同一个大于 10KB 的图片文件同时在款式文件和 j(t)sx 文件中援用

最初的摸索

给出计划后,问题其实能够被较好地解决,然而我发现 NX 应用 postcss-loader,除了最常见的 autoprefixer,还做的两件事就是应用社区的postcss-import 来解决 @import,以及应用本人写的postcss-cli-resources 来解决url()

其实社区解决 url() 也有一个插件postcss-url,临时无奈得悉为什么 NX 须要本人创立一个插件来解决。

做的这三件事中,url()@import 也能够交给 css-loader 解决,不过 NX 传入了一些门路参数,如果这样替换,咱们便无奈失常应用这些参数,例如同时开启 rebaseRootRelativeCssUrlsdeployUrl后,NX 会在打包时将 deployUrl 作为 url() 申明门路的前缀。

然而为了持续摸索 url-loader 失常工作下这些文件解析的产物和程序,我还是对配置进行了一些更改,次要包含笼罩官网的 postcss-loader 配置,仅应用其 autoprefixer 性能,以及在其后增加 css-loader,实现后输入的stats.json 如下(仅提取无效信息):

{
  "chunks": [
    {"names": ["main"],
      "files": ["main.413a212ab2c713a5f9c4.es5.js"],
      "hash": "413a212ab2c713a5f9c4",
      "modules": [
        {
          "id": "/CXp",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "name": "./app/app.scss",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
          "issuerId": null,
          "issuerName": "./app/app.tsx"
        },
        {
          "id": "JDPv",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js??ref--7-oneOf-1-0!/Users/tianzhi/dev/nx-examples/apps/cart/src/assets/arrow-right.svg",
          "name": "./assets/arrow-right.svg",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "V1gc",
          "issuerName": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            },
            {
              "id": "V1gc",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss"
            }
          ],
          "source": "export default \"data:image/svg+xml;base64,...\""
        },
        {
          "id": "LPAU",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js",
          "issuerId": "/CXp",
          "issuerName": "./app/app.scss"
        },
        {
          "id": null,
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
          "name": "./app/app.tsx",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx"
        },
        {
          "id": "V1gc",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "/CXp",
          "issuerName": "./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            }
          ],
          "source": "// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/api.js\";\nimport ___CSS_LOADER_GET_URL_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/getUrl.js\";\nimport ___CSS_LOADER_URL_IMPORT_0___ from \"../assets/add-big@2x.png\";\nimport ___CSS_LOADER_URL_IMPORT_1___ from \"../assets/arrow-right.svg\";\nimport ___CSS_LOADER_URL_IMPORT_2___ from \"../assets/banner2.png\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);\nvar ___CSS_LOADER_URL_REPLACEMENT_1___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_1___);\nvar ___CSS_LOADER_URL_REPLACEMENT_2___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_2___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".image-png{background-image:url(\"+ ___CSS_LOADER_URL_REPLACEMENT_0___ + \")}.image-svg{background-image:url(\"+ ___CSS_LOADER_URL_REPLACEMENT_1___ + \")}.image-big-png{background-image:url(\"+ ___CSS_LOADER_URL_REPLACEMENT_2___ + \")}.example>.example-inner{font-size:14px}\", \"\",{\"version\":3,\"sources\":[\"webpack://app/app.scss\"],\"names\":[],\"mappings\":\"AAAA,WAAW,wDAAgD,CAAC,WAAW,wDAAiD,CAAC,eAAe,wDAA6C,CAAC,wBAAwB,cAAc\",\"sourcesContent\":[\".image-png{background-image:url(\\\"../assets/add-big@2x.png\\\")}.image-svg{background-image:url(\\\"../assets/arrow-right.svg\\\")}.image-big-png{background-image:url(\\\"../assets/banner2.png\\\")}.example>.example-inner{font-size:14px}\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n"
        },
        {
          "id": "VNgF",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/runtime/api.js",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/runtime/api.js",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "V1gc",
          "issuerName": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            },
            {
              "id": "V1gc",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss"
            }
          ],
          "source": "..."
        },
        {
          "id": "m1aJ",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js??ref--6!/Users/tianzhi/dev/nx-examples/apps/cart/src/assets/banner2.png",
          "name": "./assets/banner2.png",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
          "issuerId": null,
          "issuerName": "./app/app.tsx",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            }
          ],
          "source": "export default __webpack_public_path__ + \"banner2.e11abd9.png\";"
        },
        {
          "id": "q/iR",
          "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/runtime/getUrl.js",
          "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/runtime/getUrl.js",
          "issuer": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
          "issuerId": "V1gc",
          "issuerName": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss",
          "issuerPath": [
            {
              "id": 0,
              "identifier": "multi /Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "multi ./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/main.tsx",
              "name": "./main.tsx"
            },
            {
              "id": null,
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/src/utils/web-babel-loader.js??ref--4!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.tsx",
              "name": "./app/app.tsx"
            },
            {
              "id": "/CXp",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/style-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "./app/app.scss"
            },
            {
              "id": "V1gc",
              "identifier": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/apps/cart/src/app/app.scss",
              "name": "/Users/tianzhi/dev/nx-examples/node_modules/css-loader/dist/cjs.js!/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src??ref--8-2!/Users/tianzhi/dev/nx-examples/node_modules/sass-loader/dist/cjs.js!./app/app.scss"
            }
          ],
          "source": "..."
        }
      ]
    }
  ]
}

解读之前,先来看 app.scss 源文件和 app.tsx 文件(仅提取援用信息):

$font: 14px;

.image-svg {background-image: url('../assets/arrow-right.svg');
}
.image-big-png {background-image: url('../assets/banner2.png');
}

.example {
  & > .example-inner {font-size: $font;}
}
import './app.scss';
import banner from '../assets/banner2.png';
import arrow from '../assets/arrow-right.svg';

其中,arrow-right.svg体积小于 10KB,而 banner2.png 大于 10KB。

解析程序为:

  • app.tsx

    • m1aJ(banner2.png)
    • /CXp(style-loader!css-loader!postcss-loader!sass-loader!app.scss)

      • LPAU(style-loader/injectStylesIntoStyleTag.js)
      • V1gc(css-loader!postcss-loader!sass-loader!app.scss)

        • JDPv(arrow-right.svg)
        • VNgF(css-loader/api.js)
        • q/iR(css-loader/getUrl.js)

能够看到,banner2.png先在 app.tsx 中被解析,而 arrow-right.svg 却在 app.scss 中被解析,对于起因,临时也没有深入研究,如果有了解的同学欢送留言。

跟后面一样,import './app.scss'被匹配解析为外部模块 /CXp(style-loader!css-loader!postcss-loader!sass-loader!app.scss),其sourceformat 后和后面统一,惟一不同的是援用的子模块多了一个css-loader,对子模块V1gc(css-loader!postcss-loader!sass-loader!app.scss)source进行 format 后失去:

// Imports
import ___CSS_LOADER_API_IMPORT___ from "../../../../node_modules/css-loader/dist/runtime/api.js";
import ___CSS_LOADER_GET_URL_IMPORT___ from "../../../../node_modules/css-loader/dist/runtime/getUrl.js";
import ___CSS_LOADER_URL_IMPORT_0___ from "../assets/arrow-right.svg";
import ___CSS_LOADER_URL_IMPORT_1___ from "../assets/banner2.png";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);
var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);
var ___CSS_LOADER_URL_REPLACEMENT_1___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_1___);
// Module
___CSS_LOADER_EXPORT___.push([
  module.id,
  ".image-svg{background-image:url(" +
    ___CSS_LOADER_URL_REPLACEMENT_0___ +
    ")}.image-big-png{background-image:url(" +
    ___CSS_LOADER_URL_REPLACEMENT_1___ +
    ")}.example>.example-inner{font-size:14px}",
  "",
  {
    version: 3,
    sources: ["webpack://app/app.scss"],
    names: [],
    mappings:
      "AAAA,WAAW,wDAAgD,CAAC,WAAW,wDAAiD,CAAC,eAAe,wDAA6C,CAAC,wBAAwB,cAAc",
    sourcesContent: ['.image-svg{background-image:url("../assets/arrow-right.svg")}.image-big-png{background-image:url("../assets/banner2.png")}.example>.example-inner{font-size:14px}',
    ],
    sourceRoot: "",
  },
]);
// Exports
export default ___CSS_LOADER_EXPORT___;

能够清晰看到,除了援用两个本身 runtime 模块外,还援用了两个图片,因为 banner2.png 当时已被解析,因而这里只须要解析arrow-right.svg

拆分 banner2.pngarrow-right.svgidentifier 能够得出它们解析时应用的 loader:

  1. banner2.pngidentifier 为:/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js??ref--6!/Users/tianzhi/dev/nx-examples/apps/cart/src/assets/banner2.png
  2. arrow-right.svgidentifier 为:/Users/tianzhi/dev/nx-examples/node_modules/url-loader/dist/cjs.js??ref--7-oneOf-1-0!/Users/tianzhi/dev/nx-examples/apps/cart/src/assets/arrow-right.svg

两者都应用了 url-loader 进行解决,不过要留神的是,banner2.png其实应用的是 url-loader 的默认 fallback loader 也就是 file-loader 解决。能够看到 banner2.png 模块的 source 为:

export default __webpack_public_path__ + \"banner2.e11abd9.png\";

arrow-right.svg 模块则为:

export default \"data:image/svg+xml;base64,...\"

总结

这次踩坑之旅总体来说效率不算高,花了一些工夫,次要是因为本人不相熟 postcss-loader 的插件机制以及 NX 的源码构造。而且最初也遗留了几个问题,只算是对 webpack debugging 的一次高级入门,心愿今后能把握第二种 DevTools 的形式,同时解决这些遗留问题。

正文完
 0