]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: filters compat
authorEvan You <yyx990803@gmail.com>
Mon, 19 Apr 2021 16:08:26 +0000 (12:08 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 19 Apr 2021 16:29:55 +0000 (12:29 -0400)
18 files changed:
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/compat/compatConfig.ts
packages/compiler-core/src/compat/transformFilter.ts [new file with mode: 0644]
packages/compiler-core/src/compile.ts
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/utils.ts
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/filter.ts [new file with mode: 0644]
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/helpers/resolveAssets.ts
packages/runtime-core/src/index.ts
packages/template-explorer/package.json
packages/template-explorer/src/options.ts

index 4a2b610f2912330da2df5fe3c7d21f21b94c445b..901f00cef156a4483c45ecc5fcfddafb94224966 100644 (file)
@@ -109,6 +109,9 @@ export interface RootNode extends Node {
   temps: number
   ssrHelpers?: symbol[]
   codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
+
+  // v2 compat only
+  filters?: string[]
 }
 
 export type ElementNode =
index e3dbbc820fc6f55c4b0175384de87c52e39327f8..cfe345f62a207f9f347a3235c15969585cc77da6 100644 (file)
@@ -50,7 +50,8 @@ import {
   CREATE_BLOCK,
   OPEN_BLOCK,
   CREATE_STATIC,
-  WITH_CTX
+  WITH_CTX,
+  RESOLVE_FILTER
 } from './runtimeHelpers'
 import { ImportItem } from './transform'
 
@@ -274,6 +275,12 @@ export function generate(
       newline()
     }
   }
+  if (__COMPAT__ && ast.filters && ast.filters.length) {
+    newline()
+    genAssets(ast.filters, 'filter', context)
+    newline()
+  }
+
   if (ast.temps > 0) {
     push(`let `)
     for (let i = 0; i < ast.temps; i++) {
@@ -458,11 +465,15 @@ function genModulePreamble(
 
 function genAssets(
   assets: string[],
-  type: 'component' | 'directive',
+  type: 'component' | 'directive' | 'filter',
   { helper, push, newline }: CodegenContext
 ) {
   const resolver = helper(
-    type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
+    __COMPAT__ && type === 'filter'
+      ? RESOLVE_FILTER
+      : type === 'component'
+        ? RESOLVE_COMPONENT
+        : RESOLVE_DIRECTIVE
   )
   for (let i = 0; i < assets.length; i++) {
     let id = assets[i]
index e73c8a1b499a3f947bc63a2ca7c9d7e539397daf..717b8dc46ad8a50e55dbfe3887019a7edd65499b 100644 (file)
@@ -22,7 +22,7 @@ export const enum CompilerDeprecationTypes {
   COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
   COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
   COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
-  COMPILER_FILTER = 'COMPILER_FILTER'
+  COMPILER_FILTERS = 'COMPILER_FILTER'
 }
 
 type DeprecationData = {
@@ -89,8 +89,11 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
     link: `https://v3.vuejs.org/guide/migration/inline-template-attribute.html`
   },
 
-  [CompilerDeprecationTypes.COMPILER_FILTER]: {
-    message: `filters have been removed in Vue 3.`,
+  [CompilerDeprecationTypes.COMPILER_FILTERS]: {
+    message:
+      `filters have been removed in Vue 3. ` +
+      `The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
+      `Use method calls or computed properties instead.`,
     link: `https://v3.vuejs.org/guide/migration/filters.html`
   }
 }
diff --git a/packages/compiler-core/src/compat/transformFilter.ts b/packages/compiler-core/src/compat/transformFilter.ts
new file mode 100644 (file)
index 0000000..bd6fd38
--- /dev/null
@@ -0,0 +1,194 @@
+import { RESOLVE_FILTER } from '../runtimeHelpers'
+import {
+  AttributeNode,
+  DirectiveNode,
+  NodeTransform,
+  NodeTypes,
+  SimpleExpressionNode,
+  toValidAssetId,
+  TransformContext
+} from '@vue/compiler-core'
+import {
+  CompilerDeprecationTypes,
+  isCompatEnabled,
+  warnDeprecation
+} from './compatConfig'
+import { ExpressionNode } from '../ast'
+
+const validDivisionCharRE = /[\w).+\-_$\]]/
+
+export const transformFilter: NodeTransform = (node, context) => {
+  if (!isCompatEnabled(CompilerDeprecationTypes.COMPILER_FILTERS, context)) {
+    return
+  }
+
+  if (node.type === NodeTypes.INTERPOLATION) {
+    // filter rewrite is applied before expression transform so only
+    // simple expressions are possible at this stage
+    rewriteFilter(node.content, context)
+  }
+
+  if (node.type === NodeTypes.ELEMENT) {
+    node.props.forEach((prop: AttributeNode | DirectiveNode) => {
+      if (
+        prop.type === NodeTypes.DIRECTIVE &&
+        prop.name !== 'for' &&
+        prop.exp
+      ) {
+        rewriteFilter(prop.exp, context)
+      }
+    })
+  }
+}
+
+function rewriteFilter(node: ExpressionNode, context: TransformContext) {
+  if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
+    parseFilter(node, context)
+  } else {
+    for (let i = 0; i < node.children.length; i++) {
+      const child = node.children[i]
+      if (typeof child !== 'object') continue
+      if (child.type === NodeTypes.SIMPLE_EXPRESSION) {
+        parseFilter(child, context)
+      } else if (child.type === NodeTypes.COMPOUND_EXPRESSION) {
+        rewriteFilter(node, context)
+      } else if (child.type === NodeTypes.INTERPOLATION) {
+        rewriteFilter(child.content, context)
+      }
+    }
+  }
+}
+
+function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
+  const exp = node.content
+  let inSingle = false
+  let inDouble = false
+  let inTemplateString = false
+  let inRegex = false
+  let curly = 0
+  let square = 0
+  let paren = 0
+  let lastFilterIndex = 0
+  let c,
+    prev,
+    i: number,
+    expression,
+    filters: string[] = []
+
+  for (i = 0; i < exp.length; i++) {
+    prev = c
+    c = exp.charCodeAt(i)
+    if (inSingle) {
+      if (c === 0x27 && prev !== 0x5c) inSingle = false
+    } else if (inDouble) {
+      if (c === 0x22 && prev !== 0x5c) inDouble = false
+    } else if (inTemplateString) {
+      if (c === 0x60 && prev !== 0x5c) inTemplateString = false
+    } else if (inRegex) {
+      if (c === 0x2f && prev !== 0x5c) inRegex = false
+    } else if (
+      c === 0x7c && // pipe
+      exp.charCodeAt(i + 1) !== 0x7c &&
+      exp.charCodeAt(i - 1) !== 0x7c &&
+      !curly &&
+      !square &&
+      !paren
+    ) {
+      if (expression === undefined) {
+        // first filter, end of expression
+        lastFilterIndex = i + 1
+        expression = exp.slice(0, i).trim()
+      } else {
+        pushFilter()
+      }
+    } else {
+      switch (c) {
+        case 0x22:
+          inDouble = true
+          break // "
+        case 0x27:
+          inSingle = true
+          break // '
+        case 0x60:
+          inTemplateString = true
+          break // `
+        case 0x28:
+          paren++
+          break // (
+        case 0x29:
+          paren--
+          break // )
+        case 0x5b:
+          square++
+          break // [
+        case 0x5d:
+          square--
+          break // ]
+        case 0x7b:
+          curly++
+          break // {
+        case 0x7d:
+          curly--
+          break // }
+      }
+      if (c === 0x2f) {
+        // /
+        let j = i - 1
+        let p
+        // find first non-whitespace prev char
+        for (; j >= 0; j--) {
+          p = exp.charAt(j)
+          if (p !== ' ') break
+        }
+        if (!p || !validDivisionCharRE.test(p)) {
+          inRegex = true
+        }
+      }
+    }
+  }
+
+  if (expression === undefined) {
+    expression = exp.slice(0, i).trim()
+  } else if (lastFilterIndex !== 0) {
+    pushFilter()
+  }
+
+  function pushFilter() {
+    filters.push(exp.slice(lastFilterIndex, i).trim())
+    lastFilterIndex = i + 1
+  }
+
+  if (
+    filters.length &&
+    warnDeprecation(
+      CompilerDeprecationTypes.COMPILER_FILTERS,
+      context,
+      node.loc
+    )
+  ) {
+    for (i = 0; i < filters.length; i++) {
+      expression = wrapFilter(expression, filters[i], context)
+    }
+    node.content = expression
+  }
+}
+
+function wrapFilter(
+  exp: string,
+  filter: string,
+  context: TransformContext
+): string {
+  context.helper(RESOLVE_FILTER)
+  const i = filter.indexOf('(')
+  if (i < 0) {
+    context.filters!.add(filter)
+    return `${toValidAssetId(filter, 'filter')}(${exp})`
+  } else {
+    const name = filter.slice(0, i)
+    const args = filter.slice(i + 1)
+    context.filters!.add(name)
+    return `${toValidAssetId(name, 'filter')}(${exp}${
+      args !== ')' ? ',' + args : args
+    }`
+  }
+}
index e3b6260bcefa14c620e9bee6dbe110acd3c629a5..78a6cb62e06d2ff322e585d1defeb3fc3fa74dd3 100644 (file)
@@ -15,6 +15,7 @@ import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
 import { transformText } from './transforms/transformText'
 import { transformOnce } from './transforms/vOnce'
 import { transformModel } from './transforms/vModel'
+import { transformFilter } from './compat/transformFilter'
 import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
 
 export type TransformPreset = [
@@ -30,6 +31,7 @@ export function getBaseTransformPreset(
       transformOnce,
       transformIf,
       transformFor,
+      ...(__COMPAT__ ? [transformFilter] : []),
       ...(!__BROWSER__ && prefixIdentifiers
         ? [
             // order is important
index f40c94c3d89efe7ae312c8d642e4e749a694c47a..94ce1ebc990a49693b6a759f8f3d2115820f6500 100644 (file)
@@ -14,6 +14,7 @@ export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
   __DEV__ ? `resolveDynamicComponent` : ``
 )
 export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
+export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
 export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
 export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
 export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
@@ -50,6 +51,7 @@ export const helperNameMap: any = {
   [RESOLVE_COMPONENT]: `resolveComponent`,
   [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
   [RESOLVE_DIRECTIVE]: `resolveDirective`,
+  [RESOLVE_FILTER]: `resolveFilter`,
   [WITH_DIRECTIVES]: `withDirectives`,
   [RENDER_LIST]: `renderList`,
   [RENDER_SLOT]: `renderSlot`,
index 36f7f2e01f5bd5814cfdfb4609d8723005081d5c..add7ea13c4b8b0f166901c2140372e7909540951 100644 (file)
@@ -118,6 +118,9 @@ export interface TransformContext
   hoist(exp: JSChildNode): SimpleExpressionNode
   cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
   constantCache: Map<TemplateChildNode, ConstantTypes>
+
+  // 2.x Compat only
+  filters?: Set<string>
 }
 
 export function createTransformContext(
@@ -289,6 +292,10 @@ export function createTransformContext(
     }
   }
 
+  if (__COMPAT__) {
+    context.filters = new Set()
+  }
+
   function addId(id: string) {
     const { identifiers } = context
     if (identifiers[id] === undefined) {
@@ -321,6 +328,10 @@ export function transform(root: RootNode, options: TransformOptions) {
   root.hoists = context.hoists
   root.temps = context.temps
   root.cached = context.cached
+
+  if (__COMPAT__) {
+    root.filters = [...context.filters!]
+  }
 }
 
 function createRootCodegen(root: RootNode, context: TransformContext) {
index 1b746111acdaa54e307aaec10747fbff84b544fa..4ebfbbe761307b5e8d6041c5561383cf034676c2 100644 (file)
@@ -254,6 +254,11 @@ export function processExpression(
       parent && parentStack.push(parent)
       if (node.type === 'Identifier') {
         if (!isDuplicate(node)) {
+          // v2 wrapped filter call
+          if (__COMPAT__ && node.name.startsWith('_filter_')) {
+            return
+          }
+
           const needPrefix = shouldPrefix(node, parent!, parentStack)
           if (!knownIds[node.name] && needPrefix) {
             if (isStaticProperty(parent!) && parent.shorthand) {
index b65c812a6e4e923d79f792b40b1204197089f1fa..370a9ac615bd3988c57c8153791ee0c4631f46bd 100644 (file)
@@ -271,7 +271,7 @@ export function injectProp(
 
 export function toValidAssetId(
   name: string,
-  type: 'component' | 'directive'
+  type: 'component' | 'directive' | 'filter'
 ): string {
   return `_${type}_${name.replace(/[^\w]/g, '_')}`
 }
index bda1501b87ad8b33e9f820c377eaefd229781849..53bb6b1aacab7566d2da45947195336a584d7dd5 100644 (file)
@@ -17,6 +17,7 @@ import { isFunction, NO, isObject } from '@vue/shared'
 import { version } from '.'
 import { installCompatMount } from './compat/global'
 import { installLegacyConfigProperties } from './compat/globalConfig'
+import { installGlobalFilterMethod } from './compat/filter'
 
 export interface App<HostElement = any> {
   version: string
@@ -43,7 +44,13 @@ export interface App<HostElement = any> {
   _context: AppContext
 
   /**
-   * @internal 2.x compat only
+   * v2 compat only
+   */
+  filter?(name: string): Function | undefined
+  filter?(name: string, filter: Function): this
+
+  /**
+   * @internal v3 compat only
    */
   _createRoot?(options: ComponentOptions): ComponentPublicInstance
 }
@@ -92,6 +99,11 @@ export interface AppContext {
    * @internal
    */
   reload?: () => void
+  /**
+   * v2 compat only
+   * @internal
+   */
+  filters?: Record<string, Function>
 }
 
 type PluginInstallFunction = (app: App, ...options: any[]) => any
@@ -307,6 +319,7 @@ export function createAppAPI<HostElement>(
 
     if (__COMPAT__) {
       installCompatMount(app, context, render, hydrate)
+      installGlobalFilterMethod(app, context)
       if (__DEV__) installLegacyConfigProperties(app.config)
     }
 
index 290aa711b54033bfa3d8a21a3b9078b278d65672..5ebe077059e0faffb322c9c1c2bb8d351e085cf9 100644 (file)
@@ -56,7 +56,9 @@ export const enum DeprecationTypes {
   COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
   COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
 
-  RENDER_FUNCTION = 'RENDER_FUNCTION'
+  RENDER_FUNCTION = 'RENDER_FUNCTION',
+
+  FILTERS = 'FILTERS'
 }
 
 type DeprecationData = {
@@ -392,6 +394,14 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
       }: false })\n` +
       `\n  (This can also be done per-component via the "compatConfig" option.)`,
     link: `https://v3.vuejs.org/guide/migration/render-function-api.html`
+  },
+
+  [DeprecationTypes.FILTERS]: {
+    message:
+      `filters have been removed in Vue 3. ` +
+      `The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
+      `Use method calls or computed properties instead.`,
+    link: `https://v3.vuejs.org/guide/migration/filters.html`
   }
 }
 
diff --git a/packages/runtime-core/src/compat/filter.ts b/packages/runtime-core/src/compat/filter.ts
new file mode 100644 (file)
index 0000000..29a5a3f
--- /dev/null
@@ -0,0 +1,18 @@
+import { App, AppContext } from '../apiCreateApp'
+import { warn } from '../warning'
+import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
+
+export function installGlobalFilterMethod(app: App, context: AppContext) {
+  context.filters = {}
+  app.filter = (name: string, filter?: Function): any => {
+    assertCompatEnabled(DeprecationTypes.FILTERS, null)
+    if (!filter) {
+      return context.filters![name]
+    }
+    if (__DEV__ && context.filters![name]) {
+      warn(`Filter "${name}" has already been registered.`)
+    }
+    context.filters![name] = filter
+    return app
+  }
+}
index 0c6c2ca8f3fec26e33582747e305ac5faf4352f0..83ee02ea5b8a2d00c1f065e7fcbd68e794dd917c 100644 (file)
@@ -268,6 +268,11 @@ export interface ComponentInternalInstance {
    * @internal
    */
   directives: Record<string, Directive> | null
+  /**
+   * Resolved filters registry, v2 compat only
+   * @internal
+   */
+  filters?: Record<string, Function>
   /**
    * resolved props options
    * @internal
index be38504bbbe00e62805727719e9ed955b6d0cbf5..2e729bfe032131c8d3023c4bc5027f95a768e7bf 100644 (file)
@@ -72,6 +72,12 @@ import {
   isCompatEnabled,
   softAssertCompatEnabled
 } from './compat/compatConfig'
+import {
+  AssetTypes,
+  COMPONENTS,
+  DIRECTIVES,
+  FILTERS
+} from './helpers/resolveAssets'
 
 /**
  * Interface for declaring custom options.
@@ -413,6 +419,9 @@ interface LegacyOptions<
   provide?: Data | Function
   inject?: ComponentInjectOptions
 
+  // assets
+  filters?: Record<string, Function>
+
   // composition
   mixins?: Mixin[]
   extends?: Extends
@@ -510,9 +519,6 @@ export function applyOptions(
     watch: watchOptions,
     provide: provideOptions,
     inject: injectOptions,
-    // assets
-    components,
-    directives,
     // lifecycle
     beforeMount,
     mounted,
@@ -721,25 +727,10 @@ export function applyOptions(
   // To reduce memory usage, only components with mixins or extends will have
   // resolved asset registry attached to instance.
   if (asMixin) {
-    if (components) {
-      extend(
-        instance.components ||
-          (instance.components = extend(
-            {},
-            (instance.type as ComponentOptions).components
-          ) as Record<string, ConcreteComponent>),
-        components
-      )
-    }
-    if (directives) {
-      extend(
-        instance.directives ||
-          (instance.directives = extend(
-            {},
-            (instance.type as ComponentOptions).directives
-          )),
-        directives
-      )
+    resolveInstanceAssets(instance, options, COMPONENTS)
+    resolveInstanceAssets(instance, options, DIRECTIVES)
+    if (__COMPAT__ && isCompatEnabled(DeprecationTypes.FILTERS, instance)) {
+      resolveInstanceAssets(instance, options, FILTERS)
     }
   }
 
@@ -818,6 +809,23 @@ export function applyOptions(
   }
 }
 
+function resolveInstanceAssets(
+  instance: ComponentInternalInstance,
+  mixin: ComponentOptions,
+  type: AssetTypes
+) {
+  if (mixin[type]) {
+    extend(
+      instance[type] ||
+        (instance[type] = extend(
+          {},
+          (instance.type as ComponentOptions)[type]
+        ) as any),
+      mixin[type]
+    )
+  }
+}
+
 export function resolveInjections(
   injectOptions: ComponentInjectOptions,
   ctx: any,
index e867cc51d57953aae57a706c6b6844084dabfa37..119bdbe6d4f78b00dbb45b382c952ce44fca8949 100644 (file)
@@ -10,8 +10,11 @@ import { camelize, capitalize, isString } from '@vue/shared'
 import { warn } from '../warning'
 import { VNodeTypes } from '../vnode'
 
-const COMPONENTS = 'components'
-const DIRECTIVES = 'directives'
+export const COMPONENTS = 'components'
+export const DIRECTIVES = 'directives'
+export const FILTERS = 'filters'
+
+export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES | typeof FILTERS
 
 /**
  * @private
@@ -44,6 +47,14 @@ export function resolveDirective(name: string): Directive | undefined {
   return resolveAsset(DIRECTIVES, name)
 }
 
+/**
+ * v2 compat only
+ * @internal
+ */
+export function resolveFilter(name: string): Function | undefined {
+  return resolveAsset(FILTERS, name)
+}
+
 /**
  * @private
  * overload 1: components
@@ -60,8 +71,11 @@ function resolveAsset(
   name: string
 ): Directive | undefined
 // implementation
+// overload 3: filters (compat only)
+function resolveAsset(type: typeof FILTERS, name: string): Function | undefined
+// implementation
 function resolveAsset(
-  type: typeof COMPONENTS | typeof DIRECTIVES,
+  type: AssetTypes,
   name: string,
   warnMissing = true,
   maybeSelfReference = false
index 42c5481bb04c4a4357fa16ea0c6b064fd312c152..3b4a16565fb6450b9fee575a092e78b8a217bf19 100644 (file)
@@ -293,6 +293,12 @@ import {
   checkCompatEnabled,
   softAssertCompatEnabled
 } from './compat/compatConfig'
+import { resolveFilter as _resolveFilter } from './helpers/resolveAssets'
+
+/**
+ * @internal only exposed in compat builds
+ */
+export const resolveFilter = __COMPAT__ ? _resolveFilter : null
 
 const _compatUtils = {
   warnDeprecation,
index f7afa8a57dc9ddd2ce0d71ea4073e30cbd0c1bc9..60117ed78cc79c9a58c832046115920210f9ddab 100644 (file)
@@ -6,6 +6,7 @@
     "formats": [
       "global"
     ],
+    "compat": true,
     "env": "development",
     "enableNonBrowserBranches": true
   },
index e8669bc7e2ea90872733fdc0dbd6636a9b4b51c8..e7b56fcce05fb5d53cd38e40de85ecc99aa00c47 100644 (file)
@@ -8,12 +8,12 @@ export const compilerOptions: CompilerOptions = reactive({
   mode: 'module',
   filename: 'Foo.vue',
   prefixIdentifiers: false,
-  optimizeImports: false,
   hoistStatic: false,
   cacheHandlers: false,
   scopeId: null,
   inline: false,
   ssrCssVars: `{ color }`,
+  compatConfig: { MODE: 3 },
   bindingMetadata: {
     TestComponent: BindingTypes.SETUP_CONST,
     setupRef: BindingTypes.SETUP_REF,
@@ -170,18 +170,20 @@ const App = {
               h('label', { for: 'inline' }, 'inline')
             ]),
 
-            // toggle optimizeImports
+            // compat mode
             h('li', [
               h('input', {
                 type: 'checkbox',
-                id: 'optimize-imports',
-                disabled: !isModule || isSSR,
-                checked: isModule && !isSSR && compilerOptions.optimizeImports,
+                id: 'compat',
+                checked: compilerOptions.compatConfig!.MODE === 2,
                 onChange(e: Event) {
-                  compilerOptions.optimizeImports = (e.target as HTMLInputElement).checked
+                  compilerOptions.compatConfig!.MODE = (e.target as HTMLInputElement)
+                    .checked
+                    ? 2
+                    : 3
                 }
               }),
-              h('label', { for: 'optimize-imports' }, 'optimizeImports')
+              h('label', { for: 'compat' }, 'v2 compat mode')
             ])
           ])
         ])