WindiCSS实现加载windi.config.ts配置文件详解

2023-02-09 12:10:27
目录
背景解惑WindiCSS IntelliSense源码unconfig源码代码实践

背景

我们知道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>
    我们将WindiCSS IntelliSense源码直接clone下来
    $ git clone https://github.com/windicss/windicss-intellisense.git
    
      找到读取配置文件的核心代码
      <!--src\lib\index.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);
          }
        }
        // ...
      
        关键实现
        <!--src\lib\index.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
          
            核心代码
            <!--src\index.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()
              }
            }
            // ...
            
              把核心代码进行精简,找到关键实现
              <!--src\index.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文件的疑惑得以解开

              代码实践

              看过没练过,等于没看过>

              我们在写脚手架的时候可以直接使用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的资料请关注易采站长站其它相关文章!