神秘 Arco 樣式出現,祭出 Webpack 解決預期外的引用問題

WindrunnerMax發表於2024-08-06

神秘 Arco 樣式出現,祭出 Webpack 解決預期外的引用問題

Webpack是現代化的靜態資源模組化管理和打包工具,其能夠透過外掛配置處理和打包多種檔案格式,生成最佳化後的靜態資源,核心原理是將各種資原始檔視為模組,透過配置檔案定義模組間的依賴關係和處理規則,從而實現模組化開發。Webpack提供了強大的外掛和載入器系統,支援了程式碼分割、熱載入和程式碼壓縮等高效構建能力,顯著提升了開發效率和效能。Webpack ResolveWebpack中用於解析模組路徑的配置項,其負責告訴Webpack如何查詢和確定模組的位置,核心功能是透過配置來定義模組的查詢機制和優先順序,從而確保Webpack能夠正確地找到和載入依賴模組。

描述

先來聊聊故事的背景,在前段時間隔壁老哥需要將大概五年前的專案逐步開發重構新版本2.0,通常如果我們開發新版本的話可能會從零啟動新專案,在新專案中重新複用元件模組,但是由於新專案時間緊任務重,並且由於專案模組眾多且結構複雜,在初版規劃中需要修改和新增的模組並非大多數,綜合評估下來從零開發新版本的成本太高,所以最終敲定的方案是依然在舊版本上逐步過渡到新版本。

當然,如果只是在原專案上修改與新增模組也不能稱為重構新版本,方案的細節是在原專案上逐步引入新的元件,而這些新的元件都是在新的package中實現的,並且以StoryBook的形式作為基本除錯環境。這麼做的原因首先是能夠保證新元件的獨立性,這樣便可以逐步替換原有元件模組,還有一個原因是在這裡新的元件還需要釋出SDK包供外部接入,這樣更便於我們複用這些元件。

那麼這件事情本來是可以有條不紊地進行下去,在新元件開發的過程中也進行地非常順利,然而在我們需要將新元件引入到原有專案中時,在這裡便出現了問題,實際上回過來看這個問題並不是很複雜,但是在不熟悉Webpack以及Less的情況下,處理起來確實需要一些時間。恰逢週五,原本是快樂的週末,然而將這個問題成功解決便用了兩天多一點的時間,在解決問題後也便有了這篇文章,當然解決的方案肯定不只是文章中提到的方法,也希望能為遇到類似問題的同學帶來一些參考。

這個問題主要是出現在樣式的處理上,五年前的專案對於一些配置確實已經不適合當前的元件模組。那麼在發現問題之後,我們就要進入經典的排查問題階段了,在歷經檢索異常丟擲原因、排除法定位問題元件、不斷生成並定位問題配置之後,最終決定在Webpack的層面上處理這些問題。實際上如果完全是我們自己的程式碼還好,如果不適配的話我們可以直接修改,問題就出現在元件引用的第三方依賴上,這些依賴的內容我們是沒有辦法強行修改的,所以我們只能藉助Webpack的能力去解決第三方依賴的問題。綜合來看,在文章中主要解決了下面三個問題:

  • less-loader樣式引用問題: 因為是最低五年前的專案,對於Webpack的管理還是使用的舊版本的Umi腳手架,且less-loader5.0.0版本,而當前最新版本已經到了12.2.0,在這其中配置和處理方式已經發生了很大變化,所以這裡需要解決less-loader的對於樣式的處理問題。
  • 元件樣式覆蓋問題: 經常用元件庫的同學應該都知道,在公司內部統一樣式規範之後,是不能夠隨意再引入原本的樣式內容的,否則就會出現樣式覆蓋的問題,但是釋出到Npm的包並不一定都遵守了這個規範,其還是有可能引用舊版本的樣式檔案,因此我們就需要避免由此造成的樣式覆蓋問題。
  • 依賴動態引入問題: 實際上在我們解決上述的問題之後,關於樣式部分的問題已經結束了,而在這裡也引申出了新的問題,我們在本質上是處理了Webpack的模組引用問題,那麼在其他場景下,例如我們需要在海外部署的服務引入專用的依賴,或者幽靈依賴造成的編譯問題,此時就需要解決動態引入依賴的問題。

針對這三個問題分別使用Webpack實現了相關DEMO,相關的程式碼都在https://github.com/WindrunnerMax/webpack-simple-environment/tree/master/packages/webpack-resolver中。

LessLoader

那麼我們先來看看less-loader的問題,當我們開啟Npm找到less-loader@5.0.0README文件時,可以看到webpack resolver一節中明確瞭如果需要從node_modules中引用樣式的話,是需要在引用路徑前加入~符號的,這樣才能讓less-loader能夠正確地從node_modules中引用樣式檔案,否則都會被認為是相對路徑匯入。

@import "~@arco-design/web-react/es/style/index.less";

在我們的專案中,其本身的依賴是沒有問題的,既然能夠編譯透過那麼必然在.less檔案的引入都是攜帶了~標識的,但是當前我們的新元件中引入的樣式檔案並沒有攜帶~標識,這就導致了less-loader無法正確地解析樣式檔案的位置,從而丟擲模組找不到的異常。如果僅僅是我們新元件中的樣式沒有攜帶標識的話,我們是可以手動加入的,然而經過排查這部分內容是新引入的元件導致的,而且還是依賴的依賴,這就導致我們無法直接修改樣式引入來解決這個問題。

那麼針對於這類問題,我們首先想到的肯定是升級less-loader的版本,但是很遺憾的是當升級到最新的12版本之後,專案同樣跑不起來,這個問題大概是根某些依賴有衝突,丟擲了一些很古怪的異常,在檢索了一段時間這個錯誤資訊之後,最終放棄了升級less-loader的方案,畢竟如果鑽牛角尖的話我們需要不斷嘗試各種依賴版本,需要花費大量的時間測試,而且也不一定能夠解決問題。

此時我們就需要換個思路,既然本質上還是less-loader的問題,而loader本質上是透過處理各種資原始檔的原始內容來處理的,那麼我們是不是可以在直接實現loader來在less-loader之前預處理.less檔案,將相關樣式的引用都加入~標識,這樣就能夠在less-loader之前將正確的.less檔案處理好。那麼在這裡的思路就是在解析到引用.less檔案的.js檔案時,將其匹配並且加入~標識,這裡只是簡單表示下正則匹配,實際需要考慮的情況還會複雜一些。

/**
 * @typedef {Record<string, unknown>} OptionsType
 * @this {import("webpack").LoaderContext<OptionsType>}
 * @param {string} source
 * @returns {string}
 */
module.exports = function (source) {
  const regexp = /@import\s+"@arco-design\/web-react\/(.*)\/index\.less";/g;
  const next = source.replace(regexp, '@import "~@arco-design/web-react/$1/index.less";');
  return next;
};

理論上這個方式是沒有問題的,但是在實際使用的過程中發現依然存在報錯的情況,只不過報錯的檔案發生了改變。經過分析之後發現這是因為在.less檔案中內部的樣式引用是由less-loader處理的,而我們編寫的loader只是針對於入口的.less檔案做了處理,深層次的.less檔案並沒有經過我們的預處理,依然會丟擲找不到模組的異常。實際上在這裡也發現了之前使用less的誤區,如果我們在.less檔案中隨意引用樣式的話,即使沒有被使用,也會被重複打包出來的,因為獨立的.less入口最終是會生成單個.css再交予後續的loader處理。

/* index.ts ok */
/* import "./index.less"; */

/* index.less ok */
@import "@arco-design/web-react-pro/es/style/index.less";

/* @arco-design/web-react-pro/es/style/index.less error */
@import "@arco-design/web-react/es/Button/style/index.less";

在這種存在多級樣式引用的情況下,我們處理起來似乎就只能關注less-loader本身的能力了,不過實際上這種情況還是不容易出現的,一般只有在複雜業務元件庫引用或者多級UI規範的情況下才可能出現。但是既然已經在我們的專案中出現了就必須要解決,幸運的是less-loader本身是支援外掛化的,我們可以透過實現less-loaderloader來處理這個問題,只不過因為文件並不完善,所以我們只能參考其他外掛的原始碼來實現。

在這裡我們就參考less-plugin-sass2less來實現,less-loader的外掛實際上是一個物件,而在這個物件中我們可以定義install方法,其中第二個引數就是外掛的管理器例項,透過在這裡呼叫addPreProcessor方法來加入我們的前處理器物件,預處理物件實現process方法即可,這樣就可以實現我們的less-loaderloader。而對於process函式的思路就比較簡單了,在這裡我們可以將其按照\n切割,在處理字串時判斷是否是相關第三方庫的@import語句,如果是的話就將其加入~標識,並且由於這是在less-loader中處理的,其引用路徑必然是樣式檔案,不需要考慮非樣式的內容引用。同時為了增加通用性,我們還可以將需要處理的元件庫名稱在例項化物件的時候傳遞進去,當然由於是偏向業務資料處理的,通用性可以沒必要很高。

// packages/webpack-resolver/src/less/import-prefix.js
module.exports = class LessImportPrefixPlugin {
  constructor(prefixModules) {
    this.prefixModules = prefixModules || [];
    this.minVersion = [2, 7, 1];
  }

  /**
   * @param {string} source
   * @param {object} extra
   * @returns {string}
   */
  process(source) {
    const lines = source.split("\n");
    const next = lines.map(line => {
      const text = line.trim();
      if (!text.startsWith("@import")) return line;
      const result = /@import ['"](.+)['"];?/.exec(text);
      if (!result || !result[1]) return line;
      const uri = result[1];
      for (const it of this.prefixModules) {
        if (uri.startsWith(it)) return `@import "~${uri}";`;
      }
      return line;
    });
    return next.join("\n");
  }

  install(less, pluginManager) {
    pluginManager.addPreProcessor({ process: this.process.bind(this) }, 3000);
  }
};

外掛已經實現了,我們同樣需要在less-loader中將其配置進去,實際上由於專案時Umi腳手架搭建起來的,修改配置就必須要藉助webpack-chain,不熟悉的話還是有些麻煩,所以我們這裡直接在rulesless-loader中將外掛配置好即可。

// packages/webpack-resolver/webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "less-loader",
            options: {
              plugins: [new LessImportPrefix(["@arco-design/web-react"])],
            },
          },
        ],
      },
      // ...
    ],
  },
  // ...
};

至此我們使用less-loaderloader解決了樣式引用的解析問題,實際上如果我們不借助less-loader的話依然可以繼續延續webpack-loader的思路來解決問題,當我們發現樣式引用的問題時,我們可以實現loader避免其內部深層次的呼叫,再將其交予專案的根目錄中將樣式重新引用出來,這樣同樣可以解決問題,但是需要我們手動分析依賴並且引入,需要一定的時間成本。

WebpackLoader

當解決了less-loader的適配問題之後,專案已然能夠成功執行起來了,但是在除錯的過程中又發現了新的問題。通常我們的專案通常都是直接引入ArcoDesign作為元件庫使用的,而內部後期推出了統一的設計規範,這個新的規範是在ArcoDesign的基礎上對元件進行了調整,我們就姑且將其命名為web-react-pro,並且引入了一套新的樣式設計,那麼這就造成了新的問題,如果在專案中引用的順序不正確,就會導致樣式的覆蓋問題。

// ok
import "@arco-design/web-react/es/style/index.less";
import "@arco-design/web-react-pro/es/style/index.less";

// error
import "@arco-design/web-react-pro/es/style/index.less";
import "@arco-design/web-react/es/style/index.less";

實際上web-react-pro內部已經幫我們實際引用了web-react的樣式,原本是不需要我們主動引入的,然而由於先前提到的並不是所有的專案都遵循了新的設計規範,特別是很多歷史三方庫的樣式引用,這就導致了我們整個引入的順序是不可控的,這就導致了樣式覆蓋問題,特別是由於我們的專案通常會配置按需引用,這就會導致部分元件設計規範是新的,部分元件的樣式是舊的,在主體頁面還是新的樣式,開啟表單之後就發現元件風格明顯發生了變化,整體UI會顯得比較混亂。

在這裡最開始的思路是想查詢出究竟是哪個三方庫導致的這個問題,然而由於專案引用關係太複雜,約定式路由的掃描還會導致實際未引用的元件依然被編譯,二分排除法查詢的過程耗費了不少時間,當然最終還是定位到了問題表單引擎元件。那麼繼續設想一下,現在的問題無非就是樣式載入順序的問題,如果我們主動控制引用到web-react的樣式是不是就能解決這個問題,除了控制import的順序之外,我們還可以透過lazy-load的形式將相關元件庫引用到專案中,也就是使原有元件先載入,之後再載入新增的元件就可以避免新樣式覆蓋。

import App from "...";
import React, { Suspense, lazy } from "react";

const Next = lazy(() => import("./component/next"));
export const LazyNextMain = (
  <Suspense fallback={<React.Fragment></React.Fragment>}>
    <Next />
  </Suspense>
);

然而很明顯這樣只是能夠暫時解決問題,如果後續需要直接在新增的元件中引入web-react的樣式,例如需要繼續基於表單引擎擴充套件功能,或者引入文件預覽元件,都會需要間接地引入web-react的樣式,如果依舊按照這個模式來處理的話就需要不斷lazy元件。那麼轉換下思路,我們是不是可以直接在Webpack的層面上直接處理這些問題,如果我們能夠直接將web-react的樣式resolve到空的檔案中,那麼就可以解決這個問題了。

實際上由於這個問題普遍存在,內部是存在Webpack的外掛來處理這個問題的,但是在我們的專案中引用會對mini-css-extract-plugin產生影響,造成一個很奇怪的異常丟擲,同樣也是經過了一段時間的排查無果之後放棄了這個方案。說到處理引用我們可能首先想到的就是babel-import-plugin這個外掛,那麼我們同樣可以實現babel的外掛來處理這個問題,而且由於場景簡單,不需要太複雜的處理邏輯。

// packages/webpack-resolver/src/loader/babel-import.js
/**
 * @param {import("@babel/core") babel}
 * @returns {import("@babel/core").PluginObj<{}>}
 */
module.exports = function (babel) {
  const { types: t } = babel;
  return {
    visitor: {
      ImportDeclaration(path) {
        const { node } = path;
        if (!node) return;
        if (node.source.value === "@arco-design/web-react/dist/css/index.less") {
          node.source = t.stringLiteral(require.resolve("./index.less"));
        }
      },
      CallExpression(path) {
        if (
          path.node.callee.name === "require" &&
          path.node.arguments.length === 1 &&
          t.isStringLiteral(path.node.arguments[0]) &&
          path.node.arguments[0].value === "@arco-design/web-react/dist/css/index.less"
        ) {
          path.node.arguments[0] = t.stringLiteral(require.resolve("./index.less"));
        }
      },
    },
  };
};

在這裡我們只需要處理import語句對應的ImportDeclaration以及require語句的CallExpression即可,當我們匹配到相關的外掛時將其替換到目標的空樣式檔案中即可,這樣就相當於抹除了所有的web-react的樣式引用,以此來解決樣式覆蓋問題。而將這個外掛加入babel也只需要在.babelrc檔案中配置下plugin引用即可。

// packages/webpack-resolver/.babelrc
{
  "plugins": ["./src/loader/babel-import.js"]
}

那麼我們還有沒有別的思路能夠解決類似的問題,假如此時我們的專案不是使用babel,而是透過ESBuild或者SWC來編譯的js檔案,那麼又該如何處理。按照我們現在的思路,究其本質是將目標的.less檔案引用重定向到空的樣式檔案中,那麼我們完全可以延續使用loader來處理的思路,實際上babel-loader也只是幫我們把純文字的內容編譯為AST得到結構化的資料方便我們使用外掛調整輸出的結果。

那麼如果依照類似於babel-loader的思路,我們處理引用端的話還是需要解析import等語句,還是會比較麻煩,而如果換個思路直接處理.less檔案,如果這個檔案的絕對路徑是從web-react中引入的,那麼我們就可以將其替換成空的樣式檔案即可,針對於.less檔案中的樣式引入,我們同樣可以採取less-loaderloader去處理這個問題。

// packages/webpack-resolver/src/loader/import-loader.js
/**
 * @typedef {Record<string, unknown>} OptionsType
 * @this {import("webpack").LoaderContext<OptionsType>}
 * @param {string} source
 * @returns {string}
 */
module.exports = function (source) {
  const regexp = /@arco-design\/web-react\/.+\.less/;
  if (regexp.exec(this.resourcePath)) {
    return "@empty: 1px;";
  }
  return source;
};

別看這段loader的實現很簡單,但是確實能夠幫助我們解決樣式覆蓋的問題,高階的食材往往只需要最簡單的烹飪方式。那麼緊接著我們只需要將loader配置到webpack當中即可,由於我們是直接配置的webpack.config.js,可以比較方便地加入規則,如果是webpack-chain等方式還是新建一個規則效率比較高。

// packages/webpack-resolver/webpack.config.js
/**
 * @typedef {import("webpack").Configuration} WebpackConfig
 * @typedef {import("webpack-dev-server").Configuration} WebpackDevServerConfig
 * @type {WebpackConfig & {devServer?: WebpackDevServerConfig}}
 */
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
          require.resolve("./src/loader/import-loader"),
        ],
      },
      // ...
    ],
  },
  // ...
};

WebpackResolver

在這裡我們的樣式引入問題已經解決了,總結起來我們實際上就是透過各種方式處理了WebpackResolve問題,所以最開始的就提到了這並不是個複雜的問題,只是因為我們並不熟悉這部分能力導致需要探索問題所在以及解決方案。那麼在Webpack中針對於Resolve的問題是不是有什麼更通用的解決方案,實際上在Webpack中提供了resolve.plugins這個配置項,我們可以透過這個配置項來定義Resolve的外掛,這樣就可以在WebpackResolve階段處理模組的查詢和解析。

我們首先來設想一個場景,當我們的專案需要專屬的部署服務,例如我們需要在海外引入專有的依賴版本,這個依賴主要API與通用版本並無差別,主要是一些合規的資料上報介面等等,但是問題來了,專有版本和通用版本的包名是不一樣的,如果是我們希望在編譯時直接處理這個問題而不是需要人工維護版本的話,可行的一個解決方案是在編譯之前透過指令碼將相關依賴alias為海外的包版本,如果還有深層依賴的話同樣需要透過包管理器鎖定版本,這樣就可以解決多版本的維護問題。

// package.json
{
  "dependencies": {
    // common
    "package-common": "1.0.0",
    // oversea
    "package-common": "npm:package-oversea@1.0.0",
  }
}

然而透過指令碼不斷修改package.json的配置還是有些麻煩,並且每次修改過後還是需要重新安裝依賴,這樣的操作顯然不夠友好,那麼我們可以考慮下更優雅的一些方式,我們可以在package.json中預先安裝好commonoversea的版本依賴,然後在Webpackresolve.alias檔案中動態修改相關依賴的alias

module.exports = {
  resolve: {
    alias: {
      "package-common": process.env.OVERSEA ? "package-oversea" : "package-common",
    },
  },
}

那麼如果我們需要更細粒度的控制,例如由於幽靈依賴的問題我們不能將所有的包版本都alias為統一版本,去年我就遇到了Yarn+Next.js導致的core.js依賴衝突問題,絕大部分以來都是3版本,而某些依賴就是需要2版本卻被錯誤地resolve3版本了,這種情況下就需要控制某些模組的resolve行為來解決這類問題。

熟悉vite的同學都知道,基於@rollup/plugin-alias外掛在vite中的alias還提供了更高階的配置,其可以支援我們動態處理alias行為,除了find/replace這種基於正則的解析方式之外,還支援傳遞ResolveFunction/ResolveObject的形式用來處理Rollup解析時的Hook行為。

// rollup/dist/rollup.d.ts
export type ResolveIdResult = string | NullValue | false | PartialResolvedId;
export type ResolveIdHook = (
	this: PluginContext,
	source: string,
	importer: string | undefined,
	options: { attributes: Record<string, string>; custom?: CustomPluginOptions; isEntry: boolean }
) => ResolveIdResult;

// vite/dist/node/index.d.ts
interface ResolverObject {
  buildStart?: PluginHooks['buildStart']
  resolveId: ResolverFunction
}
interface Alias {
  find: string | RegExp
  replacement: string
  customResolver?: ResolverFunction | ResolverObject | null
}
type AliasOptions = readonly Alias[] | { [find: string]: string }

實際上Webpack中還內建了NormalModuleReplacementPlugin外掛來更加靈活地處理模組引用的替換問題,在使用的時候直接呼叫new webpack.NormalModuleReplacementPlugin(resourceRegExp, newResource)即可,需要注意的是newResource是支援函式形式的,如果需要修改其行為則直接原地修改context引數物件即可,而且context引數中攜帶了大量的資訊,我們完全可以藉助其攜帶的資訊判斷解析來源。

// webpack/types.d.ts
// https://github.com/webpack/webpack/blob/main/lib/NormalModuleReplacementPlugin.js
declare interface ModuleFactoryCreateDataContextInfo {
	issuer: string;
	issuerLayer?: null | string;
	compiler: string;
}
declare interface ResolveData {
	contextInfo: ModuleFactoryCreateDataContextInfo;
	resolveOptions?: ResolveOptions;
	context: string;
	request: string;
	assertions?: Record<string, any>;
	dependencies: ModuleDependency[];
	dependencyType: string;
	createData: Partial<NormalModuleCreateData & { settings: ModuleSettings }>;
	fileDependencies: LazySet<string>;
	missingDependencies: LazySet<string>;
	contextDependencies: LazySet<string>;
	/**
	 * allow to use the unsafe cache
	 */
	cacheable: boolean;
}
declare class NormalModuleReplacementPlugin {
	constructor(resourceRegExp: RegExp, newResource: string | ((arg0: ResolveData) => void));
}

NormalModuleReplacementPlugin是透過NormalModuleFactorybeforeResolve來實現的,然而這裡還是具有一定的侷限性,其只能處理我們應用本身的依賴解析,而例如我們的第一個問題中,less-loader@5.0是主動排程LoaderContext.resolve方法來執行檔案解析的,也就是說這是loader藉助webpack的能力來實現本身的檔案解析需要,而NormalModuleReplacementPlugin是無法處理這種情況的。

// less-loader@5.0.0/dist/createWebpackLessPlugin.js
const resolve = pify(loaderContext.resolve.bind(loaderContext));
loadFile(filename, currentDirectory, options) {
  // ...
  const moduleRequest = loaderUtils.urlToRequest(url, url.charAt(0) === '/' ? '' : null);
  const context = currentDirectory.replace(trailingSlash, '');
  let resolvedFilename;
  return resolve(context, moduleRequest).then(f => {
    resolvedFilename = f;
    loaderContext.addDependency(resolvedFilename);
    if (isLessCompatible.test(resolvedFilename)) {
      return readFile(resolvedFilename).then(contents => contents.toString('utf8'));
    }
    return loadModule([stringifyLoader, resolvedFilename].join('!')).then(JSON.parse);
    
    // ...
  })
  // ...
}

那麼這時候就需要用到我們的resolve.plugins了,我們可以將resolve完全作為一個獨立的模組來看待,當然其本身也是基於enhanced-resolve來實現的,而我們在這裡實現的外掛相當於對解析行為實現了Hook,因此即使類似於less-loader這種獨立排程的外掛也能正常排程,而且此配置在webpack2中就已經實現了,已經是非常通用的能力。那麼我們可以基於這個能力在before-hook的鉤子來解決我們之前提到第二個問題,即樣式覆蓋的問題。

// packages/webpack-resolver/src/resolver/import-resolver.js
module.exports = class ImportResolver {
  constructor() {}

  /**
   * @typedef {Required<import("webpack").Configuration>["resolve"]} ResolveOptionsWebpackOptions
   * @typedef {Exclude<Required<ResolveOptionsWebpackOptions>["plugins"]["0"], "...">} ResolvePluginInstance
   * @typedef {Parameters<ResolvePluginInstance["apply"]>["0"]} Resolver
   * @param {Resolver} resolver
   */
  apply(resolver) {
    const target = resolver.ensureHook("resolve");

    resolver
      .getHook("before-resolve")
      .tapAsync("ImportResolverPlugin", (request, resolveContext, callback) => {
        const regexp = /@arco-design\/web-react\/.+\.less/;
        const prev = request.request;
        const next = require.resolve("./index.less");
        if (regexp.test(prev)) {
          const newRequest = { ...request, request: next };
          return resolver.doResolve(
            target,
            newRequest,
            `Resolved ${prev} to ${next}`,
            resolveContext,
            callback
          );
        }
        return callback();
      });
  }
};

// packages/webpack-resolver/webpack.config.js
module.exports = {
  // ...
  resolve: {
    plugins: [new ImportResolver()],
  },
  // ...
}

因為其對less-loader同樣也會生效,我們同樣也可以匹配解析內容,將其處理為正確的引用地址,這樣就不用實現less-loaderloader來處理這個問題了,也就是說我們可以透過一個外掛來同時解決兩個問題。並且前邊提到的差異化解析問題也可以透過requestresolveContext引數來確定來源,由此來處理特定條件下的引用或者幽靈依賴帶來的編譯問題等等。

// index.less => @import "@arco-design/web-react/es/style/index.less"
 {
  context: {},
  path: '/xxx/webpack-simple-environment/packages/webpack-resolver/src/less',
  request: './@arco-design/web-react/es/style/index.less'
}

// index.ts => import "./index.less"
{
  context: {
    issuer: '/xxx/webpack-simple-environment/packages/webpack-resolver/src/less/index.ts',
    issuerLayer: null,
    compiler: undefined
  },
  path: '/xxx/webpack-simple-environment/packages/webpack-resolver/src/less',
  request: './index.less'
}

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://webpack.js.org/api/loaders
https://webpack.js.org/configuration/resolve/#resolveplugins
https://github.com/webpack/enhanced-resolve?tab=readme-ov-file#plugins
https://github.com/less/less-docs/blob/master/content/tools/plugins.md
https://github.com/less/less-docs/blob/master/content/features/plugins.md
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md

相關文章