Búsqueda de sitios web

typescript - Cómo hacer que ts-transformer-keys funcione con Vue


editar :

ahora me siento como un idiota... he estado trabajando en esto desde ayer por la tarde. Hoy me di cuenta de que podría probar configurando transpileOnly en false:

  chainWebpack: config => {
    const getCustomTransformers = program => ({
      before: [keysTransformer(program)]
    });
    const transpileOnly = false;

    ["ts", "tsx"].forEach(rule => {
      config.module
        .rule(rule)
        .use("ts-loader")
        .loader("ts-loader")
        .tap(options => Object.assign(options, { getCustomTransformers, transpileOnly }));
    });
  },

Y.. esto FUNCIONA. Veo las teclas de mi interfaz en la consola. Mi única pregunta ahora es: ¿qué estoy rompiendo al hacer esto? Si no es seguro hacerlo de esta manera, ¿hay alguna otra manera mejor de hacerlo?

orig :

En este momento, mi sospecha (del último párrafo) es que la opción transpileOnly: true, de ts-loader es incompatible con los transformadores... si es así, ¿hay algo que se pueda hacer al respecto? Solo una confirmación o negación de mi idea sería bastante impresionante. El transformador en cuestión es ts-transformer-keys de Kenji Imamula

Creo que necesitas estar familiarizado tanto con webpack como con TypeScript para lograr que esto funcione. Si eres como yo, lo usas profesionalmente pero no estás involucrado en la funcionalidad central, podrías apreciar esta introducción sobre transformadores: cómo escribir un complemento de transformación de TypeScript

Soy bastante capaz cuando se trata de webpack, así que esperaba hacer que esto funcionara. Pero, estoy usando Vue 3 por primera vez, y el webpack es... diferente. Usas un vue.config.js para configurar un bloque para modificar la configuración de webpack con webpack-merge o webpack-chain. Como quiero modificar los loaders de ts (supuse), usé chain... hay algunos otros cambios de configuración que he hecho, así que mi vue.config.ts se ve así:

const PackageVars = require('./package-vars.webpack-plugin.js')
const keysTransformer = require('ts-transformer-keys/transformer').default

module.exports = {
  configureWebpack: {
    plugins: [PackageVars]
  },

  chainWebpack: config => {
    const getCustomTransformers = program => ({
      before: [keysTransformer(program)]
    })

    ;['ts', 'tsx'].forEach(rule => {
      config.module
        .rule(rule)
        .use('ts-loader')
        .loader('ts-loader')
        .tap(options => Object.assign(options, { getCustomTransformers }))
    })
  },

  pluginOptions: {
    i18n: {
      locale: 'en',
      fallbackLocale: 'en',
      localeDir: 'locales',
      enableInSFC: true
    }
  },

  css: {
    loaderOptions: {
      sass: {
        prependData: `@import "@/styles/global.scss";`
      }
    }
  }
}

y eso genera un webpack.config.js (según reporta vue inspect, esto está abreviado, principalmente eliminando la compilación relacionada con css, debido a los límites de caracteres en stackoverflow):

{
  mode: 'development',
  context: '/Users/me/Public/project',
  node: {
    setImmediate: false,
    process: 'mock',
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  //...
  resolveLoader: {
    modules: [
      '/Users/me/Public/project/node_modules/@vue/cli-plugin-typescript/node_modules',
      '/Users/me/Public/project/node_modules/@vue/cli-plugin-babel/node_modules',
      'node_modules',
      '/Users/me/Public/project/node_modules',
      '/Users/me/Public/project/node_modules/@vue/cli-service/node_modules'
    ]
  },
  module: {
    noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
    rules: [
      /* config.module.rule('vue') */
      {
        test: /\.vue$/,
        use: [
          {
            loader: '/Users/me/Public/project/node_modules/cache-loader/dist/cjs.js',
            options: {
              cacheDirectory: '/Users/me/Public/project/node_modules/.cache/vue-loader',
              cacheIdentifier: '1fc0539f'
            }
          },
          {
            loader: '/Users/me/Public/project/node_modules/vue-loader/lib/index.js',
            options: {
              compilerOptions: {
                whitespace: 'condense'
              },
              cacheDirectory: '/Users/me/Public/project/node_modules/.cache/vue-loader',
              cacheIdentifier: '1fc0539f'
            }
          }
        ]
      },
      //...
      /* config.module.rule('js') */
      {
        test: /\.m?jsx?$/,
        exclude: [
          function () { /* omitted long function */ }
        ],
        use: [
          {
            loader: '/Users/me/Public/project/node_modules/cache-loader/dist/cjs.js',
            options: {
              cacheDirectory: '/Users/me/Public/project/node_modules/.cache/babel-loader',
              cacheIdentifier: '0fff5c38'
            }
          },
          {
            loader: '/Users/me/Public/project/node_modules/babel-loader/lib/index.js'
          }
        ]
      },
      /* config.module.rule('eslint') */
      {
        enforce: 'pre',
        test: /\.(vue|(j|t)sx?)$/,
        exclude: [
          /node_modules/,
          '/Users/me/Public/project/node_modules/@vue/cli-service/lib'
        ],
        use: [
          {
            loader: '/Users/me/Public/project/node_modules/eslint-loader/index.js',
            options: {
              extensions: [
                '.js',
                '.jsx',
                '.vue',
                '.ts',
                '.tsx'
              ],
              cache: true,
              cacheIdentifier: '5749dfb1',
              emitWarning: false,
              emitError: false,
              eslintPath: '/Users/me/Public/project/node_modules/eslint',
              formatter: function () { /* omitted long function */ }
            }
          }
        ]
      },
      /* config.module.rule('ts') */
      {
        test: /\.ts$/,
        use: [
          {
            loader: '/Users/me/Public/project/node_modules/cache-loader/dist/cjs.js',
            options: {
              cacheDirectory: '/Users/me/Public/project/node_modules/.cache/ts-loader',
              cacheIdentifier: 'b1785fd2'
            }
          },
          {
            loader: '/Users/me/Public/project/node_modules/babel-loader/lib/index.js'
          },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              appendTsSuffixTo: [
                '\\.vue$'
              ],
              happyPackMode: false,
              getCustomTransformers: program => ({
                before: [keysTransformer(program)]
              })
            }
          }
        ]
      },
      /* config.module.rule('tsx') */
      {
        test: /\.tsx$/,
        use: [
          {
            loader: '/Users/me/Public/project/node_modules/cache-loader/dist/cjs.js',
            options: {
              cacheDirectory: '/Users/me/Public/project/node_modules/.cache/ts-loader',
              cacheIdentifier: 'b1785fd2'
            }
          },
          {
            loader: '/Users/me/Public/project/node_modules/babel-loader/lib/index.js'
          },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              happyPackMode: false,
              appendTsxSuffixTo: [
                '\\.vue$'
              ],
              getCustomTransformers: program => ({
                before: [keysTransformer(program)]
              })
            }
          }
        ]
      },
      /* config.module.rule('i18n') */
      {
        resourceQuery: /blockType=i18n/,
        type: 'javascript/auto',
        use: [
          {
            loader: '@kazupon/vue-i18n-loader'
          }
        ]
      }
    ]
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    },
    minimizer: [
      {
        options: {
          test: /\.m?js(\?.*)?$/i,
          chunkFilter: () => true,
          warningsFilter: () => true,
          extractComments: false,
          sourceMap: true,
          cache: true,
          cacheKeys: defaultCacheKeys => defaultCacheKeys,
          parallel: true,
          include: undefined,
          exclude: undefined,
          minify: undefined,
          terserOptions: {
            compress: {
              arrows: false,
              collapse_vars: false,
              comparisons: false,
              computed_props: false,
              hoist_funs: false,
              hoist_props: false,
              hoist_vars: false,
              inline: false,
              loops: false,
              negate_iife: false,
              properties: false,
              reduce_funcs: false,
              reduce_vars: false,
              switches: false,
              toplevel: false,
              typeofs: false,
              booleans: true,
              if_return: true,
              sequences: true,
              unused: true,
              conditionals: true,
              dead_code: true,
              evaluate: true
            },
            mangle: {
              safari10: true
            }
          }
        }
      }
    ]
  },
  plugins: [
    /* config.plugin('vue-loader') */
    new VueLoaderPlugin(),
    /* config.plugin('define') */
    new DefinePlugin(
      {
        'process.env': {
          NODE_ENV: '"development"',
          VUE_APP_I18N_LOCALE: '"en"',
          VUE_APP_I18N_FALLBACK_LOCALE: '"en"',
          BASE_URL: '"/"'
        }
      }
    ),
    /* config.plugin('case-sensitive-paths') */
    new CaseSensitivePathsPlugin(),
    /* config.plugin('friendly-errors') */
    new FriendlyErrorsWebpackPlugin(
      {
        additionalTransformers: [
          function () { /* omitted long function */ }
        ],
        additionalFormatters: [
          function () { /* omitted long function */ }
        ]
      }
    ),
    /* config.plugin('html') */
    new HtmlWebpackPlugin(
      {
        templateParameters: function () { /* omitted long function */ },
        template: '/Users/me/Public/project/public/index.html'
      }
    ),
    /* config.plugin('pwa') */
    new HtmlPwaPlugin(
      {
        name: 'project'
      }
    ),
    /* config.plugin('preload') */
    new PreloadPlugin(
      {
        rel: 'preload',
        include: 'initial',
        fileBlacklist: [
          /\.map$/,
          /hot-update\.js$/
        ]
      }
    ),
    /* config.plugin('prefetch') */
    new PreloadPlugin(
      {
        rel: 'prefetch',
        include: 'asyncChunks'
      }
    ),
    /* config.plugin('copy') */
    new CopyPlugin(
      [
        {
          from: '/Users/me/Public/project/public',
          to: '/Users/me/Public/project/dist',
          toType: 'dir',
          ignore: [
            '.DS_Store',
            {
              glob: 'index.html',
              matchBase: false
            }
          ]
        }
      ]
    ),
    /* config.plugin('fork-ts-checker') */
    new ForkTsCheckerWebpackPlugin(
      {
        vue: true,
        tslint: false,
        formatter: 'codeframe',
        checkSyntacticErrors: false
      }
    ),
    {
      definitions: {
        'process.env': {
          PACKAGE_VERSION: '"2.0.0"',
          PACKAGE_NAME: '"project"'
        }
      }
    }
  ],
  entry: {
    app: [
      './src/main.ts'
    ]
  }
}

en mi código agregué la demo del readme a una interfaz que quiero usar (esto está en un archivo .ts, no en un archivo .vue, así que realmente medio esperaba que esto funcionara):

import { keys } from "ts-transformer-keys";

export interface FirebaseUser {
  uid: string;
  displayName?: string;
  email?: string;
  phoneNumber?: string;
  emailVerified: boolean;
  isAnonymous?: boolean;
}

const keysOfProps = keys<FirebaseUser>();

console.log({ keysOfProps });

esto debería simplificar/reducir mucho la deuda de métodos de fábrica como los siguientes:

export function factoryFirebaseUser(
  data = <Partial<FirebaseUser>>{}
): FirebaseUser {
  if (!data.hasOwnProperty("uid")) {
    data.uid = undefined;
  }
//...

  return data as FirebaseUser;
}

pero cuando ejecuto npm run serve obtengo: Uncaught ReferenceError: keys is not defined .. ¿puedes ayudarme a encontrar dónde más necesito añadirlo?

Me pregunto si las opciones transpileOnly: true, que son necesarias en la configuración del cargador son las culpables. Creo que esto es para permitir que babel se configure por separado.

Respuesta #1

así que sí, agregar transpileOnly: true funciona. Abrí un informe de errores preguntando si eso es seguro o no, y lo investigué en los servidores oficiales de Discord: en resumen, nadie que esté activo lo sabe. Pero al menos funciona.