Removing all data-test attributes from Vue templates during production build in Vue 3

Viewed 1381

I work with Vue3 in TS (last vue-cli).

I want to get all nodes (vnodes) elements when vue-loader compile .vue file. I need to read nodes attributes and remove all "data-test".

I have try in vue.config.js to use :

module.exports = {
  chainWebpack: (config) => {
    config.module
      .rule('vue')
      .use('vue-loader')
      // .loader('vue-loader')                       // same with
      .tap((options) => {
        options.compilerOptions = {
          ...(options.compilerOptions || {}),
modules: [                                           // never enter here
            {
              preTransformNode(node) {
                // if (process.env.NODE_ENV === 'production') {
                const { attrsMap, attrsList } = node
                console.log(node)
                if (attrsMap['qa-id']) {
                  delete attrsMap['qa-id']
                  const index = attrsList.findIndex(
                    (x) => x.name === 'data-test'
                  )

                  attrsList.splice(index, 1)
                }
                // }

                return node
              }
            }
          ]
        }

        return options
      })
  }
}

I know the transformation is done inside vue-template-compiler. How can I enter in compile hook ?

I have try to use preTransformNode in module but that fail.

Sources :

2 Answers

The main problem here is that you are working with vue-template-compiler documentation, but that package is the compiler for Vue 2!

In Vue 3, compiler is split into multiple packages and is missing proper documentation as of now (or I was just unable to find it)

Also there were significant changes in the API - instead of modules, you pass nodeTransforms (source) and transforms are not objects, just functions.

Luckily for you, there is a interesting video on YT presented by Vue core member Rahul Kadyan which shows the exact use case you need (removing data-test attributes) - code

So I guess the code should look like this:

function removeDataTestAttrs(node) {
  if (node.type === 1 /* NodeTypes.ELEMENT */) {
    node.props = node.props.filter(prop =>
      prop.type === 6 /* NodeTypes.ATTRIBUTE */
        ? prop.name !== 'data-test'
        : true
    )
  }
}

module.exports = {
  parallel: false, // !!IMPORTANT!! - see note below
  chainWebpack: (config) => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => {
        options.compilerOptions = {
          ...(options.compilerOptions || {}),
          nodeTransforms: [removeDataTestAttrs]                                                                                                        
        }

        return options
      })
  }
}

Note - the problem mentioned in comments (solution working with serve but throws errors on build) is caused by Vue CLI using thread-loader for production builds. The problem is that while using thread-loader, you can not pass a functions as part of Webpack config (see this warning in the docs) so setting parralel: false is required to make it work....

Vite (Update - 22.06.22)

// vite.config.ts
function removeDataTestAttrs(node) {
  if (node.type === 1 /* NodeTypes.ELEMENT */) {
    node.props = node.props.filter(prop =>
      prop.type === 6 /* NodeTypes.ATTRIBUTE */
        ? prop.name !== 'data-test'
        : true
    )
  }
}

export default defineConfig(() => {
  return {
    plugins: [
      vue({
        template: {
          compilerOptions: {
            nodeTransforms: isProd ? [removeDataTestAttrs] : [],
          },
        },
      }),
    ]
  }
})

Vue-CLI 5 + Vue 3.2:

const { defineConfig } = require('@vue/cli-service');

function removeAttributesDuringBuild (node) {
  const attributesToRemove = [
    'data-test',
    ':data-test',
    'v-bind:data-test',
    'data-value',
    ':data-value',
    'v-bind:data-value'
  ];
  const nodeIsElement = node.type === 1; // ENUMS ARE STUPID
  if (nodeIsElement) {
    node.props = node.props.filter(function (prop) {
      const propIsAttribute = prop.type === 6; // ENUMS ARE STUPID
      const propIsDynamicAttribute = prop.name === 'bind';
      if (propIsAttribute) {
        const attributeName = prop.name;
        return !attributesToRemove.includes(attributeName);
      }
      if (propIsDynamicAttribute) {
        const attributeName = prop.arg?.content;
        return !attributesToRemove.includes(attributeName);
      }
      return true;
    });
  }
}

module.exports = defineConfig({
  lintOnSave: false,
  transpileDependencies: true,
  parallel: false, // disabled to allow for node transforms
  chainWebpack: (config) => {
    // Remove comments during build
    config.optimization
      .minimizer('terser')
      .tap((args) => {
        args[0].terserOptions.output = {
          ...args[0].terserOptions.output,
          comments: false
        };
        return args;
      });

    // Remove dev attributes
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(function (options) {
        options.compilerOptions = {
          ...(options.compilerOptions || {}),
          nodeTransforms: [removeAttributesDuringBuild]
        };
        return options;
      });
  }
});
Related