Skip to content

Latest commit

 

History

History
379 lines (282 loc) · 9.53 KB

File metadata and controls

379 lines (282 loc) · 9.53 KB

logo

unplugin-preprocessor-directives

npm version npm downloads bundle License JSDocs

English | 简体中文

安装

npm i unplugin-preprocessor-directives

Important

此插件应该放在配置中所有其他插件之前,以确保预处理器指令首先被处理。

Vite
// vite.config.ts
import PreprocessorDirectives from 'unplugin-preprocessor-directives/vite'

export default defineConfig({
  plugins: [
    PreprocessorDirectives({ /* options */ }), // 应该是第一个插件
  ],
})

Example: playground/


Rollup
// rollup.config.js
import PreprocessorDirectives from 'unplugin-preprocessor-directives/rollup'

export default {
  plugins: [
    PreprocessorDirectives({ /* options */ }),
  ],
}


Webpack
// webpack.config.js
module.exports = {
  /* ... */
  plugins: [
    require('unplugin-preprocessor-directives/webpack')({ /* options */ })
  ]
}


Nuxt
// nuxt.config.js
export default defineNuxtConfig({
  modules: [
    ['unplugin-preprocessor-directives/nuxt', { /* options */ }],
  ],
})

This module works for both Nuxt 2 and Nuxt Vite


Vue CLI
// vue.config.js
module.exports = {
  configureWebpack: {
    plugins: [
      require('unplugin-preprocessor-directives/webpack')({ /* options */ }),
    ],
  },
}


esbuild
// esbuild.config.js
import { build } from 'esbuild'
import PreprocessorDirectives from 'unplugin-preprocessor-directives/esbuild'

build({
  plugins: [PreprocessorDirectives()],
})


Rspack (⚠️ 实验性)
// rspack.config.js
module.exports = {
  plugins: [
    require('unplugin-preprocessor-directives/rspack')({ /* options */ }),
  ],
}


使用

插件选项

插件接受以下配置选项:

preserveLineNumbers

  • 类型: boolean
  • 默认值: false

启用后,插件将通过用空行替换被删除的代码来保留行号,而不是实际删除它。这对于调试很有用,因为它确保错误消息和调试器中的行号与原始源代码匹配。

PreprocessorDirectives({
  preserveLineNumbers: true,
})

示例:

原始代码:

const message = 'Hello'

// #if DEV

console.log('Development mode')

// #endif

const result = compute()

使用 preserveLineNumbers: false(默认):

const message = 'Hello'

const result = compute()

使用 preserveLineNumbers: true

const message = 'Hello'

const result = compute()

cwd

  • 类型: string
  • 默认值: process.cwd()

当前工作目录。用于解析 #include 指令中的相对路径。

include

  • 类型: string | RegExp | (string | RegExp)[]
  • 默认值: ['**/*']

要处理的文件。支持 glob 模式。

exclude

  • 类型: string | RegExp | (string | RegExp)[]
  • 默认值: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/]

要从处理中排除的文件。支持 glob 模式。

directives

  • 类型: Directive[]
  • 默认值: []

除了内置指令之外要添加的自定义指令。有关更多信息,请参见自定义指令

定义 symbols

您可以使用以下两个预处理器指令来定义或取消定义 symbols,以便进行条件编译:

  • #define: 定义一个 symbol.
  • #undef: 取消定义一个 symbol.

使用 #define 可以定义一个 symbol。将 symbol 作为表达式传递给 #if 指令时,表达式的值将为 true,如下例所示:

// #define VERBOSE

// #if VERBOSE
console.log('Verbose output version')
// #endif

条件编译

  • #if: 打开条件编译,只有当指定的 symbol 被定义并求值为 true 时,代码才会被编译。
  • #elif:关闭前面的条件编译,并判断是否定义了指定的 symbol 并求值为 true 时,打开一个新的条件编译。
  • #else: 如果前一个指定的 symbol 未定义或求值为 false,则关闭前一个条件编译,并打开一个新的条件编译。
  • #endif: 关闭前面的条件编译。

Note

默认情况下,使用 vite 的 loadEnv 函数根据process.env.NODE_ENV 加载环境变量并作为条件编译 symbols。

// src/index.ts

// #if DEV
console.log('Debug version')
// #endif

// #if !MYTEST
console.log('MYTEST is not defined or false')
// #endif

可以使用运算符 == (相等)和 != (不等)来测试 truefalsetrue 表示 symbol 已定义。语句 #if DEBUG#if (DEBUG == true) 意义相同。支持使用 && (与)、|| (或) 和 ! (非) 操作符来判断是否定义了多个 symbols。还可以用括号将 symbols 和运算符分组。

class MyClass {
  constructor() {
    // #if (DEBUG && MYTEST)
    console.log('DEBUG and MYTEST are defined')
    // #elif (DEBUG==false && !MYTEST)
    console.log('DEBUG and MYTEST are not defined')
    // #endif
  }
}

错误、警告和信息提示

可以指示编译器生成用户定义的编译器错误、警告和信息。

  • #error: 生成一条错误消息,但不会终止编译。
  • #warning: 生成一条警告消息。
  • #info: 生成一条信息消息。
// #error this is an error message
// #warning this is a warning message
// #info this is an info message

当然,也可以和条件编译结合使用:

// #if DEBUG
// #info Debug mode is on
// #endif
// #if !DEBUG
// #info Debug mode is off
// #endif

#include 指令

您可以使用 #include 指令将其他文件的内容包含到当前文件中。被包含的文件也会经过预处理器处理。

Warning

#include 指令是一个编译时文本替换工具,主要用于以下场景:

  • 在不同环境下包含不同的配置代码片段
  • 与条件编译结合使用,根据编译条件包含不同的代码
  • 共享需要预处理的代码片段

它不能也不应该替代:

  • JavaScript/TypeScript 的 importrequire - 用于模块化和依赖管理
  • CSS 的 @import - 用于样式表的模块化
  • HTML 的模板系统或组件系统

如果您只是想要模块化代码,请使用语言原生的模块系统。只有在需要编译时处理和条件包含时才使用 #include

该指令支持以下两种语法:

// #include "path/to/file"
or
// #include <path/to/file>

Note

  1. 循环引用: 如果文件 A 包含文件 B,而文件 B 又包含文件 A,会自动检测并阻止循环引用,只处理一次
  2. 路径解析: 相对路径是相对于配置的工作目录(cwd)解析的
  3. 文件扩展名: 可以包含任何类型的文本文件,不限于 .js 文件
  4. 嵌套处理: 包含的文件会完整地通过预处理器,所以可以使用所有支持的指令

自定义指令

您可以使用 defineDirective 定义自己的指令。

以内置指令为例:

export const MessageDirective = defineDirective<MessageToken, MessageStatement>(context => ({
  lex(comment) {
    return simpleMatchToken(comment, /#(error|warning|info)\s*(.*)/)
  },
  parse(token) {
    if (token.type === 'error' || token.type === 'warning' || token.type === 'info') {
      this.current++
      return {
        type: 'MessageStatement',
        kind: token.type,
        value: token.value,
      }
    }
  },
  transform(node) {
    if (node.type === 'MessageStatement') {
      switch (node.kind) {
        case 'error':
          context.logger.error(node.value, { timestamp: true })
          break
        case 'warning':
          context.logger.warn(node.value, { timestamp: true })
          break
        case 'info':
          context.logger.info(node.value, { timestamp: true })
          break
      }
      return createProgramNode()
    }
  },
  generate(node, comment) {
    if (node.type === 'MessageStatement' && comment)
      return `${comment.start} #${node.kind} ${node.value} ${comment.end}`
  },
}))

enforce: 'pre' | 'post'

指令的执行优先级

  • pre 尽可能早执行
  • post 尽可能晚执行