https://github.com/morevm/v-bem-transformer
Intuitive and performant BEM in Vue files via pseudo-directive
https://github.com/morevm/v-bem-transformer
Last synced: 10 months ago
JSON representation
Intuitive and performant BEM in Vue files via pseudo-directive
- Host: GitHub
- URL: https://github.com/morevm/v-bem-transformer
- Owner: MorevM
- License: mit
- Created: 2022-02-06T16:07:06.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2024-06-08T10:03:04.000Z (about 2 years ago)
- Last Synced: 2025-03-10T01:31:56.197Z (over 1 year ago)
- Language: TypeScript
- Size: 1.95 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE.md
Awesome Lists containing this project
README

[](https://opensource.org/licenses/MIT)




# @morev/v-bem-transformer
Intuitive and performant BEM in Vue files via directive syntax 🛠
✔️ Supports `Vue 2 / 3` both; \
✔️ Supports `Nuxt 2 / 3` both; \
✔️ Provides a composable to use with Composition API; \
✔️ Best BEM practices for single-file-components; \
✔️ Small footprint (1kb gzipped).
## Table of contents
* [What's the point, what does it do?](#whats-the-point-what-does-it-do)
* [Installation](#installation)
* [Usage](#usage)
* [Usage with Nuxt](#usage-with-nuxt)
* [Configuration](#configuration)
* [How does it work](#how-does-it-work)
* [Recipes](#recipes)
* [Known limitations](#known-limitations)
## What's the point, what does it do?
The package helps level out one of [BEM](https://en.bem.info/)'s biggest problems - its verbosity. \
It also allows you to have more confidence that there are no errors in the block name,
and that nothing unnecessary has been added to the component's classes that relate to other components.
Implementation details are described in the ["How does it work"](#how-does-it-work) section,
more examples and recipes are shown in the ["Recipes"](#recipes) section,
here is just a self-explanatory code example:
Using the package, if you provide the code like that...
```vue
Some title
defineOptions({ name: 'the-block' });
```
...it will be rendered into the following:
```html
Some title
```
You can also use variables for modifiers or elements, making it easier than ever to handle states.
## Installation
> [!CAUTION]
> Requirements:
>
> * Node version: `>= 18.0.0`;
> * Nuxt version (if used): `>= 2.17.0 || >= 3.5.0`;
> * Any bundler is required: `vite`, `esbuild`, `webpack`, `rollup` are supported via [unplugin](https://github.com/unjs/unplugin).
>
> **The plugin will not work if you are using a Node or Nuxt version less than the specified ones.**
---
### Using `yarn`
```bash
yarn add @morev/v-bem-transformer
```
---
### Using `npm`
```bash
npm install @morev/v-bem-transformer
```
---
### Using `pnpm`
```bash
pnpm add @morev/v-bem-transformer
```
---
### Using `bun`
```bash
bun add @morev/v-bem-transformer
```
## Usage
> [!Note]
> You may skip this section if you are going to use the module with Nuxt. \
> [Go to "Usage with Nuxt" section](#usage-with-nuxt).
### Step 1: Bundler plugin
First, you need to attach the plugin to your builder (`vite` is used here in the example):
> [!IMPORTANT]
> The plugin SHOULD be the first in the chain to work correctly.
```ts
import { defineConfig } from 'vite';
import pluginVue from '@vitejs/plugin-vue';
import { vitePlugin as pluginVBem } from '@morev/v-bem-transformer';
export default defineConfig({
plugins: [
pluginVBem({
// custom options described below (and also fully typed via TS right here)
}),
pluginVue(),
],
});
```
The packages provides plugins for `vite`, `rollup`, `webpack` and `esbuild`.
### Step 2: Vue plugin
> [!NOTE]
> This guide illustrates how to use it with `Vue 3`. \
> Connecting to `Vue 2` follows the same algorithm, except for the specifics of installing plugins -
> you need to use `Vue.use()` instead of `app.use()`.
```ts
import { createApp } from 'vue';
import App from './App.vue';
import { vuePlugin as pluginVBem } from '@morev/v-bem-transformer/vue';
const app = createApp(App)
app.use(pluginVBem({
// custom options described below (and also typed with TS right here)
}));
app.mount('#app');
```
### Step 3: Types for `b()` method (if needed)
If you are going to use the function generating BEM classes directly (quite rarely used to be honest) and you need a type inside a component,
add the following to your `tsconfig.json`:
```json
{
"compilerOptions": {
"types": [
"@morev/v-bem-transformer/types/vue-globals.d.ts"
]
}
}
```
> [!WARNING]
> `vue-globals.d.ts` registers a property named `b`. \
> If you are going to use a different property name - you must provide the appropriate types yourself.
### Step 4: Composable (if needed)
If you need to access BEM generator function within ``, you can create you own `useBem` composable this way:
```ts
// ~/composables/use-bem.ts
import { useBemFactory } from '@morev/v-bem-transformer/use-bem-factory';
export const useBem = useBemFactory({
// custom options described below
});
```
> [!TIP]
> You can find this composable template with typings [here](https://github.com/MorevM/v-bem-transformer/blob/master/src/nuxt-templates/use-bem.ts).
## Usage with Nuxt
The package supports both `Nuxt 2` and `Nuxt 3`. \
Nuxt 2 support without Bridge is slightly limited - the module will not automatically register the `useBem` composable
(but you still can do it yourself, for example if you are using [`@nuxtjs/composition-api`](https://github.com/nuxt-community/composition-api)).
[Install the package](#installation), next add `@morev/v-bem-transformer/nuxt` to the `modules` section of your `nuxt.config`:
```ts
export default defineNuxtConfig({
modules: [
'@morev/v-bem-transformer/nuxt',
],
vBemTransformer: {
// Optional configuration options described below.
}
});
// ...or using the tuple syntax:
export default defineNuxtConfig({
modules: [
['@morev/v-bem-transformer/nuxt', {
// Optional configuration options described below.
}],
],
});
```
Using Nuxt 3, no additional steps are required, just start using the `v-bem` directive and composable `useBem` within your components. \
It will be fully typed by default.
## Configuration
All methods have built-in documentation via TS,
source types are available [here](https://github.com/MorevM/v-bem-transformer/blob/master/src/types.ts).
## How does it work
The package works in two steps:
1. Registers a global mixin that provides a [BEM class name generator](https://github.com/MorevM/bem-classnames) bound to each component. \
You can [check it out here](https://github.com/MorevM/v-bem-transformer/blob/master/src/vue.ts).
1. At build time, using regular expressions, replaces `v-bem` directives with class declarations (preserving existing ones, if any)
that call the method added using the mixin in the previous step:
```html
<!-- Before the transformation -->
<div v-bem>
<div v-bem:element="{ modifier: true }">
<div v-bem:inner :class="dynamicClass"></div>
</div>
</div>
<!-- After the transformation -->
<div :class="b(null)">
<div :class="b('element', { modifier: true })">
<div :class="[dynamicClass, b('inner')]"></div>
</div>
</div>
```
### Why not just use directives?
A directive is a separate entity with its own lifecycle, so using it actually for each DOM element is overkill. \
If we use transformation, we just get class bindings.
Also directives require separate processing at the SSR level, which adds complexity and points of failure (especially in Vue 2).
## Recipes
<details>
<summary>How to use with static classnames?</summary>
<br />
There are two use cases: writing static classes via the directive modifier syntax, or using the native `class` attribute.
**Input**:
```vue
<template>
<div v-bem.static.another-static>
<div v-bem:element class="is-active"></div>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'the-block' });
```
**Output**:
```html
```
How to use with dynamic static classnames?
As well as in the previous case, you have two options: writing via the dynamic directive modifier syntax, or using the native `class` attribute.
**Input**:
```vue
defineOptions({ name: 'the-block' });
const dynamicClassBinding = ref('is-active');
```
**Output**:
```html
```
Handling states with dynamic modifier bindings
**Input**:
```vue
Toggle
defineOptions({ name: 'the-block' });
const isActive = ref(false);
```
**Initial output**:
```html
Toggle
```
**Output after click on the button**:
```html
Toggle
```
All variables within `v-bem` directive are fully reactive.
## Known limitations
As the module manipulates the source code via a trivial regular expression, there is no support for JSX/TSX and programmatically created elements
(using Vue's `h()` method for example).
You still can use `useBem()` composable using Composition API and `this.b()` to access `bemFunction`,
but transforming as a directive will only work in Vue files that do not use a custom syntax like `pug`.
How to deal with JSX/TSX using Composition API?
```vue
defineOptions({ name: 'the-block' });
const $b = useBem();
const render = () => (
<div class={$b()}>
<div class={$b('inner-element')}></div>
</div>
);
```
How to deal with render function using Options API?
```vue
import { h } from 'vue';
export default {
name: 'the-block',
render() {
return h('div', { class: this.b() }, [
h('div', { class: this.b('element') }, 'Some content')
])
}
}
```