npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@morev/v-bem-transformer

v1.2.5

Published

Intuitive and performant BEM in Vue files via directive syntax

Downloads

68

Readme

Stability of "master" branch License: MIT Last commit Release version GitHub Release Date Keywords

@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?

The package helps level out one of BEM'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" section, more examples and recipes are shown in the "Recipes" section, here is just a self-explanatory code example:

Using the package, if you provide the code like that...

<template>
  <div v-bem>
    <div v-bem:header>
      <div v-bem:title="{ size: 'large', wide: true }">
        Some title
      </div>
    </div>
  </div>
</template>

<script setup>
  defineOptions({ name: 'the-block' });
</script>

...it will be rendered into the following:

<div class="the-block">
  <div class="the-block__header">
    <div class="
      the-block__title 
      the-block__title--size-large 
      the-block__title--wide
    ">
      Some title
    </div>
  </div>
</div>

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.

The plugin will not work if you are using a Node or Nuxt version less than the specified ones.


Using yarn

yarn add @morev/v-bem-transformer

Using npm

npm install @morev/v-bem-transformer

Using pnpm

pnpm add @morev/v-bem-transformer

Using bun

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.

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.

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().

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:

{
  "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 <script setup>, you can create you own useBem composable this way:

// ~/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.

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).

Install the package, next add @morev/v-bem-transformer/nuxt to the modules section of your nuxt.config:

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.

How does it work

The package works in two steps:

  1. Registers a global mixin that provides a BEM class name generator bound to each component.
    You can check it out here.
  2. 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:
<!-- 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

Input:

<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' });
</script>

Output:

<div class="the-block static another-static">
  <div class="the-block__element is-active"></div>
</div>

Input:

<template>
  <div v-bem.[dynamicClassBinding]>
    <div v-bem:element :class="dynamicClassBinding"></div>
  </div>
</template>

<script lang="ts" setup>
  defineOptions({ name: 'the-block' });
  
  const dynamicClassBinding = ref('is-active');
</script>

Output:

<div class="the-block static another-static">
  <div class="the-block__element is-active"></div>
</div>

Input:

<template>
  <div v-bem>
    <div v-bem:element="{ active: isActive }"></div>
    <button type="button" @click="isActive = !isActive">Toggle</button>
  </div>
</template>

<script lang="ts" setup>
  defineOptions({ name: 'the-block' });
  
  const isActive = ref(false);
</script>

Initial output:

<div class="the-block">
  <div class="the-block__element"></div>
  <button type="button">Toggle</button>
</div>

Output after click on the button:

<div class="the-block">
  <div class="the-block__element the-block__element--active"></div>
  <button type="button">Toggle</button>
</div>

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.

<script lang="ts" setup>
  defineOptions({ name: 'the-block' });
  
  const $b = useBem();
  
  const render = () => (
    <div class={$b()}>
      <div class={$b('inner-element')}></div>
    </div>
  );
</script>
<script lang="ts">
  import { h } from 'vue';
  
  export default {
    name: 'the-block',
    render() {
      return h('div', { class: this.b() }, [
        h('div', { class: this.b('element') }, 'Some content')
      ])
    }
  }
</script>