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

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 :
https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme
https://vue-loader.vuejs.org/options.html

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;
});
}
});

Vite 4 + Vue 2.7
import vue from '#vitejs/plugin-vue2';
import { defineConfig } from 'vite';
function removeAttributesDuringBuild (astEl) {
const attributesToRemove = [
'data-test',
':data-test',
'v-bind:data-test',
'data-value',
':data-value',
'v-bind:data-value'
];
function removeAttribute (attributesMap, attributesList, attributeToRemove) {
if (attributesMap[attributeToRemove]) {
delete attributesMap[attributeToRemove];
const index = attributesList.findIndex(function (attribute) {
return attribute.name === attributeToRemove;
});
attributesList.splice(index, 1);
}
}
if (process.env.NODE_ENV === 'production') {
const { attrsMap, attrsList } = astEl;
attributesToRemove.forEach(function (attributeToRemove) {
removeAttribute(attrsMap, attrsList, attributeToRemove);
});
}
return astEl;
}
export default defineConfig(() => {
return {
plugins: [
vue({
template: {
compilerOptions: {
modules: [
{
preTransformNode: removeAttributesDuringBuild
}
]
}
}
})
]
};
});

Related

React native couldn't resolve local module after noHoist has been added to project

I have this monorepo js setup with yarn workspaces and lerna
/package.json
/packages
/common (js shared code)
/package.json
/mobile (react native - metro)
/package.json
/web (CRA)
/package.json
Mobile and web packages are importing common package inside package.json as follow
"dependencies": {
"common": "*",
}
I had to add noHoist option in root package.json so that mobile native dependencies don't get hoisted so build scripts still run fine
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": [
"**/react-native",
"**/react-native/**"
]
}
Web did work fine before and after adding noHoist option
React native metro bundling start failing after adding noHoist .. it shows
"Error: Unable to resolve module .. could not be found within the project or in these directories:
node_modules
../../node_modules"
However common package does actually exists under root node_modules ?
Looks like some kind of a linking issue ! (did try to link it manually/ same issue) .. note that I didn't add common package under noHoist
here how my metro config looks like
const path= require('path');
const watchFolders = [
path.resolve(`${__dirname}`), // Relative path to package node_modules
path.resolve(`${__dirname}/../../node_modules`), // Relative path to root node_modules ];
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),},
maxWorkers: 2,
watchFolders, };
ANY IDEA ? 🧐
Turns out the issue was in bundling, fixed by editing metro.config.js to include blocklist and extraNodeModules
const path = require('path');
const exclusionList = require('metro-config/src/defaults/exclusionList');
const getWorkspaces = require('get-yarn-workspaces');
function generateAssetsPath(depth, subpath) {
return `/assets`.concat(
Array.from({ length: depth })
// eslint-disable-next-line no-unused-vars
.map((_, i) => `/${subpath}`)
.join(''),
);
}
function getMetroAndroidAssetsResolutionFix(params = {}) {
const { depth = 3 } = params;
let publicPath = generateAssetsPath(depth, 'dir');
const applyMiddleware = (middleware) => (req, res, next) => {
// eslint-disable-next-line no-plusplus
for (let currentDepth = depth; currentDepth >= 0; currentDepth--) {
const pathToReplace = generateAssetsPath(currentDepth, 'dir');
const replacementPath = generateAssetsPath(depth - currentDepth, '..');
if (currentDepth === depth) {
publicPath = pathToReplace;
}
if (req.url.startsWith(pathToReplace)) {
req.url = req.url.replace(pathToReplace, replacementPath);
break;
}
}
return middleware(req, res, next);
};
return {
publicPath,
applyMiddleware,
};
}
function getNohoistedPackages() {
// eslint-disable-next-line global-require
const monorepoRootPackageJson = require('../../package.json');
const nohoistedPackages = monorepoRootPackageJson.workspaces.nohoist
.filter((packageNameGlob) => !packageNameGlob.endsWith('**'))
.map((packageNameGlob) => packageNameGlob.substring(3));
return nohoistedPackages;
}
function getMetroNohoistSettings({
dir,
workspaceName,
reactNativeAlias,
} = {}) {
const nohoistedPackages = getNohoistedPackages();
const blockList = [];
const extraNodeModules = {};
nohoistedPackages.forEach((packageName) => {
extraNodeModules[packageName] =
reactNativeAlias && packageName === 'react-native'
? path.resolve(dir, `./node_modules/${reactNativeAlias}`)
: path.resolve(dir, `./node_modules/${packageName}`);
const regexSafePackageName = packageName.replace('/', '\\/');
blockList.push(
new RegExp(
`^((?!${workspaceName}).)*\\/node_modules\\/${regexSafePackageName}\\/.*$`,
),
);
});
return { extraNodeModules, blockList };
}
const workspaces = getWorkspaces(__dirname);
const androidAssetsResolutionFix = getMetroAndroidAssetsResolutionFix({
depth: 3,
});
const nohoistSettings = getMetroNohoistSettings({
dir: __dirname,
workspaceName: 'mobile',
});
module.exports = {
transformer: {
// Apply the Android assets resolution fix to the public path...
// publicPath: androidAssetsResolutionFix.publicPath,
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
// server: {
// // ...and to the server middleware.
// enhanceMiddleware: (middleware) =>
// androidAssetsResolutionFix.applyMiddleware(middleware),
// },
// Add additional Yarn workspace package roots to the module map.
// This allows importing importing from all the project's packages.
watchFolders: [
path.resolve(__dirname, '../../node_modules'),
...workspaces.filter((workspaceDir) => !(workspaceDir === __dirname)),
],
maxWorkers: 2,
resolver: {
// Ensure we resolve nohoisted packages from this directory.
blockList: exclusionList(nohoistSettings.blockList),
extraNodeModules: nohoistSettings.extraNodeModules,
},
};
You can check this universal CRA/RN mono-repo that uses such metro configs

react native environment variables .env return undefined

I am currently using dotenv but there seems to be some caching issue with the #env. So wanted to try using process.env but it returns undefined. I am using expo, dotenv and webpack.
On app.js process.env.REACT_APP_KEY returns undefined, already restarted server, terminal and even my PC.
.env
REACT_APP_KEY=aaddddawrfffvvvvssaa
REACT_APP_KEY = aaddddawrfffvvvvssa
Webpack config
const createExpoWebpackConfigAsync = require('#expo/webpack-config');
module.exports = async function (env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
const path = require('path')
config.module.rules = config.module.rules.map(rule => {
if (rule.oneOf) {
let hasModified = false;
const newRule = {
...rule,
oneOf: rule.oneOf.map(oneOfRule => {
if (oneOfRule.use && oneOfRule.use.loader && oneOfRule.use.loader.includes('babel-loader')) {
oneOfRule.include = [
path.resolve('.'),
path.resolve('node_modules/#ui-kitten/components'),
]
}
if (oneOfRule.test && oneOfRule.test.toString().includes('svg')) {
hasModified = true;
const test = oneOfRule.test.toString().replace('|svg', '');
return {...oneOfRule, test: new RegExp(test)};
} else {
return oneOfRule;
}
})
};
// Add new rule to use svgr
// Place at the beginning so that the default loader doesn't catch it
if (hasModified) {
newRule.oneOf.unshift({
test: /\.svg$/,
exclude: /node_modules/,
use: [
{
loader: '#svgr/webpack',
}
]
});
}
return newRule;
} else {
return rule;
}
});
return config;
};
babel config
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin',
['module:react-native-dotenv', {
'moduleName': '#env',
'path': '.env',
"blocklist": null,
"allowlist": null,
"safe": true,
"allowUndefined": false,
}]
],
};
};
If it matters (for dotenv)
declare module '#env' {
export const API_ENDPOINT: string;
}
Also tried process.env.NODE_ENV (which is working and prints "development" as output). Only process.env.VARIABLE_NAME is undefined
Maintainer here! process.env support in react-native-dotenv was just added this month https://github.com/goatandsheep/react-native-dotenv/issues/187

Vite is converting dynamic import statement to __import__ during build. Any way to I could fix this?

I am trying to use vite in one of my existing project. After long hard work I finally managed to make everything work in development mode. But, when I tried to test the app after building scripts with vite build, all dynamic imports failed for me. The issue was, all the import statements were being converted to __import__. If I manually replace __import__ with import on built bundles, then everything works. I tried removing #vitejs/plugin-legacy but, it still did not work.
Here is my vite.config.ts file
import { UserConfigFn } from 'vite';
import RubyPlugin from 'vite-plugin-ruby';
import FullReload from 'vite-plugin-full-reload';
import styleLint from '#amatlash/vite-plugin-stylelint';
import eslintPlugin from 'vite-plugin-eslint';
import legacy from '#vitejs/plugin-legacy';
import { resolve as _resolve, join } from 'path';
import * as tsconfig from './tsconfig.json';
const paths = tsconfig.compilerOptions.paths;
const defaultAlias = Object.keys(paths).reduce((acc, key) => {
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
const value = paths[key][0];
const path: string = key.replace('/*', '/');
acc.push({
find: path,
replacement: _resolve(__dirname, value.replace('/*', '/').replace('.//', './')) + '/',
});
return acc;
}, [] as any[]);
const configFn: UserConfigFn = ({ mode, command }) => {
const plugins =
mode === 'development' && command === 'serve'
? [
styleLint({
exclude: ['node_modules', 'public', 'plyr.css'],
}),
eslintPlugin({
fix: true,
exclude: ['node_modules', '**/legacy.js'],
}),
FullReload(['config/routes.rb', 'app/views/**/*']),
]
: [];
return {
plugins: [...plugins, legacy({}), RubyPlugin()],
css: {
postcss: '',
},
resolve: {
alias: [
...defaultAlias,
{
find: /~(.+)/,
replacement: join(process.cwd(), 'node_modules/$1'),
},
],
},
build: {
sourcemap: process.env.RAILS_ENV !== 'production',
polyfillDynamicImport: true,
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes('node_modules')) {
if (id.includes('jquery')) {
return 'jquery';
}
if (
/creditcards|snabbdom-form|email-validator|format-numbe|form-serialize|phone-regex|email-regex|currency-regex|format-number|snake-case|number-format|superagent/.test(
id
)
) {
return 'formHelpers';
}
if (id.includes('chart.js')) {
return 'chartJs';
}
if (id.includes('moment')) {
return 'momentJs';
}
if (id.includes('imagesloaded')) {
return 'imagesLoaded';
}
if (id.includes('uuid')) {
return 'uuid';
}
if (id.includes('flimflam')) {
return 'flimflam';
}
if (/cropperjs|guillotine/.test(id)) {
return 'imageHelpers';
}
if (/ff-dashboard|ff-file-uploader/.test(id)) {
return 'ffDashboard';
}
return 'vendor';
}
},
},
},
},
clearScreen: false,
};
};
export default configFn;
Turns out it was because polyfillDynamicImport to true.

Can not use CSS modules in Nextjs, Ant Design?

I'm doing a NextJS project using Ant design, Less. But I can't use CSS modules with Less even when I add
cssModules: true,
Here my next.config.js
const withPlugins = require("next-compose-plugins");
const withLess = require('#zeit/next-less')
const lessToJS = require('less-vars-to-js')
const fs = require('fs')
const path = require('path')
// Where your antd-custom.less file lives
const themeVariables = lessToJS(
fs.readFileSync(path.resolve(__dirname, './assets/antd-custom.less'), 'utf8')
)
module.exports = withLess({
cssModules: true,
lessLoaderOptions: {
javascriptEnabled: true,
modifyVars: themeVariables, // make your antd custom effective
},
webpack: (config, { isServer }) => {
if (isServer) {
const antStyles = /antd\/.*?\/style.*?/
const origExternals = [...config.externals]
config.externals = [
(context, request, callback) => {
if (request.match(antStyles)) return callback()
if (typeof origExternals[0] === 'function') {
origExternals[0](context, request, callback)
} else {
callback()
}
},
...(typeof origExternals[0] === 'function' ? [] : origExternals),
]
config.module.rules.unshift({
test: antStyles,
use: 'null-loader',
})
}
return config
},
})
index.js:
import styles from 'path-to-less.less'
<p className={styles.styleTab1} >Conntents </p>
but I it doesn't effect to my component.
Looks like this is a solution:
https://github.com/vercel/next.js/issues/8156#issuecomment-516009764
From my tries, this solution requires #zeit/next-less, #zeit/next-css, less, less-loader as dependencies 🤷🏽‍♂️.

Share class between front and back (nuxt.js)

I 'm trying to share a simple class between vue and express.
The problem is that vue supports es6 import/export default, whereas express(node) only supports the require/module.exports syntax.
Is there a way to use export default statement in nuxt.js backend (express.js), so I can use my class in both ends? Or maybe, add support for module.exports in vue?
Thanks!
You can exlude you file from transpiler.
const Path = require('path')
module.exports = {
build:{
extend({ module: { rules } }) {
const rule = findJsxRule(rules)
rule.exclude = exclude(
[
Path.resolve(__dirname, '../lib'),
Path.resolve(__dirname, '../interface')
],
rule.exclude
)
}
}
}
function findJsxRule(rules) {
return rules.find(rule => {
return rule.test.toString() === '/\\.jsx?$/i'
})
}
function exclude(paths, old) {
return file => {
if (paths.some(path => file.startsWith(path))) {
return true
} else if (old) {
return old(file)
} else {
return false
}
}
}