首頁 > 軟體

WindiCSS實現載入windi.config.ts組態檔詳解

2023-02-08 22:01:34

背景

我們知道WindiCSS的組態檔既支援js字尾也支援ts字尾,即:windi.config.jswindi.config.ts

我們在 vscode 安裝的WindiCSS IntelliSense也支援讀取多種字尾格式的組態檔。vscode 基於 electron 實現,electron 底層是一個node.js + v8的整合終端,支援執行 js 格式的程式碼,WindiCSS IntelliSense最終打包出來的執行的程式碼也是 js 格式的程式碼

WindiCSS IntelliSense是怎麼實現 js 檔案載入 ts 檔案的呢?

這個問題困惑了我大半天,正好最近在寫腳手架,需要支援載入 ts 字尾的組態檔

於是下定決心,把這個問題搞清楚,最後終於在原始碼裡找到了答案

解惑

WindiCSS IntelliSense原始碼

  • 我們將WindiCSS IntelliSense原始碼直接clone下來
$ git clone https://github.com/windicss/windicss-intellisense.git
  • 找到讀取組態檔的核心程式碼
<!--srclibindex.ts-->
// ...
async init() {
    try {
      const config = await this.loadConfig();
      this.processor = new Processor(
        config
      ) as Processor;
      this.attrPrefix = this.processor.config('attributify.prefix') as
        | string
        | undefined;
      this.variants = this.processor.resolveVariants();
      this.colors = flatColors(
        this.processor.theme('colors', {}) as colorObject
      );
      this.register();
    } catch (error) {
      Log.error(error);
    }
  }
  // ...
  • 關鍵實現
<!--srclibindex.ts-->
// ...
import { loadConfig } from 'unconfig';
// ...
async loadConfig(file?: string) {
    if(!workspace.workspaceFolders) return;
    const { config, sources } = await loadConfig<Config>({
      sources: [
        {
          files: 'windi.config',
          extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs'],
        },
        {
          files: 'tailwind.config',
          extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs'],
        },
      ],
      merge: false,
      cwd: workspace.workspaceFolders[0].uri.fsPath,
    });
    Log.info(`Loading Config File: ${sources}`);
    return config;
  }
// ...

從關鍵實現的程式碼上看,我們就找到了答案:使用unconfig來實現載入不同字尾的檔案

看到這裡,我們還是沒有搞懂:為什麼js能載入ts,我們繼續深入瞭解一下unconfig的內部實現

unconfig原始碼

  • 我們將unconfig原始碼直接clone下來
git clone https://github.com/antfu/unconfig.git
  • 核心程式碼
<!--srcindex.ts-->
// ...
import jiti from 'jiti'
// ...
async function loadConfigFile<T>(filepath: string, source: LoadConfigSource<T>): Promise<LoadConfigResult<T> | undefined> {
  let config: T | undefined
  let parser = source.parser || 'auto'
  let bundleFilepath = filepath
  let code: string | undefined
  async function read() {
    if (code == null)
      code = await fs.readFile(filepath, 'utf-8')
    return code
  }
  if (source.transform) {
    const transformed = await source.transform(await read(), filepath)
    if (transformed) {
      bundleFilepath = join(dirname(filepath), `__unconfig_${basename(filepath)}`)
      await fs.writeFile(bundleFilepath, transformed, 'utf-8')
      code = transformed
    }
  }
  if (parser === 'auto') {
    try {
      config = JSON.parse(await read())
      parser = 'json'
    }
    catch {
      parser = 'require'
    }
  }
  try {
    if (!config) {
      if (typeof parser === 'function') {
        config = await parser(filepath)
      }
      else if (parser === 'require') {
        config = await jiti(filepath, {
          interopDefault: true,
          cache: false,
          requireCache: false,
          v8cache: false,
          esmResolve: true,
        })(bundleFilepath)
      }
      else if (parser === 'json') {
        config = JSON.parse(await read())
      }
    }
    if (!config)
      return
    const rewritten = source.rewrite
      ? await source.rewrite(config, filepath)
      : config
    if (!rewritten)
      return undefined
    return {
      config: rewritten,
      sources: [filepath],
    }
  }
  catch (e) {
    if (source.skipOnError)
      return
    throw e
  }
  finally {
    if (bundleFilepath !== filepath)
      await fs.unlink(bundleFilepath).catch()
  }
}
// ...
  • 把核心程式碼進行精簡,找到關鍵實現
<!--srcindex.ts-->
// ...
import jiti from 'jiti'
// ...
async function loadConfigFile<T>(filepath: string, source: LoadConfigSource<T>): Promise<LoadConfigResult<T> | undefined> {
// ...
    try {
    if (!config) {
      if (typeof parser === 'function') {
        config = await parser(filepath)
      }
      else if (parser === 'require') {
        config = await jiti(filepath, {
          interopDefault: true,
          cache: false,
          requireCache: false,
          v8cache: false,
          esmResolve: true,
        })(bundleFilepath)
      }
      else if (parser === 'json') {
        config = JSON.parse(await read())
      }
    }
    // ...
}
// ...

從關鍵實現的程式碼上看,我們就找到了答案:使用jiti來實現 js 檔案載入 ts 檔案時,動態編譯 ts 檔案並返回結果。

jiti檔案中這麼描述:Runtime typescript and ESM support for Node.js (CommonJS),我們可以更加粗暴地理解為require-ts

為了讓大家更好地理解unconfig的工作流程,樓主根據上述的unconfig核心程式碼,整理出一個unconfig核心工作原理流程圖

關於js檔案如何載入ts檔案的疑惑得以解開

程式碼實踐

看過沒練過,等於沒看過 - B 站夢覺教游泳

我們在寫腳手架的時候可以直接使用unconfig讀取組態檔即可,例如:讀取vite.config.ts可以這麼實現

import { loadConfig } from 'unconfig'
const { config } = await loadConfig({
  sources: [
    {
      files: 'vite.config',
      async rewrite(config) {
        return await (typeof config === 'function' ? config() : config)
      },
    },
  ]
})

以上就是WindiCSS實現載入windi.config.ts組態檔詳解的詳細內容,更多關於WindiCSS載入windi.config.ts的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com