Skip to content

Svelte preprocessor to generate CSS Modules classname on Svelte components

License

Notifications You must be signed in to change notification settings

non25/svelte-preprocess-cssmodules

 
 

Repository files navigation

Svelte preprocess CSS Modules

npm install --save-dev svelte-preprocess-cssmodules

Generate CSS Modules classname on Svelte components

Use of the style tag

Write css rules inside <style> and prefix on the HTML markup any classname that require CSS Modules by $style => $style.MY_CLASSNAME .

<style>
  .red { color: red; }
</style>

<p class="$style.red">My red text</p>

The component will be transformed to

<style>
  .red-30_1IC { color: red; }
</style>

<p class="red-30_1IC">My red text</p>

Process required class

CSS Modules classname are generated to the html class values prefixed by $style. The rest is left untouched and will use the default svelte scoped class.

Before

<style>
  .blue { color: blue; }
  .red { color: red; }
  .text-center { text-align: center; }
</style>

<p class="blue text-center">My blue text</p>
<p class="$style.red text-center">My red text</p>

After

<style>
  .blue.svelte-1s2ez3w { color: blue;}
  .red-2iBDzf { color: red; }
  .text-center.svelte-1s2ez3w { text-align: center; }
</style>

<p class="blue text-center svelte-1s2ez3w">My blue text</p>
<p class="red-2iBDzf text-center svelte-1s2ez3w">My red text</p>

Remove unspecified class

On non strict mode, if a CSS Modules class has no css style attached, it will be removed from the class attribute.

Before

<style>
  .text-center { text-align: center; }
</style>

<p class="$style.blue text-center">My blue text</p>

After

<style>
  .text-center.svelte-1s2ez3w { text-align: center; }
</style>

<p class="text-center svelte-1s2ez3w">My blue text</p>

Target any classname format

kebab-case or camelCase, name the classes the way you're more comfortable with.

Before

<style>
  .red { color: red; }
  .red-crimson { color: crimson; }
  .redMajenta { color: magenta; }
</style>

<span class="$style.red">Red</span>
<span class="$style.red-crimson">Crimson</span>
<span class="$style.redMajenta">Majenta</span>

After

<style>
  .red-2xTdmA { color: red; }
  .red-crimson-1lu8Sg { color: crimson; }
  .redMajenta-2wdRa3 { color: magenta; }
</style>

<span class="red-2xTdmA">Red</span>
<span class="red-crimson-1lu8Sg">Crimson</span>
<span class="redMajenta-2wdRa3">Majenta</span>

Work with class directive

Toggle a class on an element.

<script>
  let isActive = true;
</script>

<style>
  .bold { font-weight: bold; }
</style>

<p class:$style.bold={isActive}>My red text</p>
<p class="{isActive ? '$style.bold' : ''}">My blue text</p>

Shorthand

To remove verbosity the shorthand $.MY_CLASSNAME can be used instead of the regular $style.MY_CLASSNAME.

before

<script>
  let isActive = true;
</script>

<style>
  .red { color: red; }
  .blue { color: blue; }
  .bold { font-weight: bold; }
</style>

<p
  class:$.bold={isActive}
  class="$.red">My red text</p>
<p class="{isActive ? '$.bold' : ''} $.blue">My blue text</p>

After

<style>
  .red-en-6pb { color: red; }
  .blue-oVk-n1 { color: blue; }
  .bold-2jIMhI { font-weight: bold; }
</style>

<p class="red-en-6pb bold-2jIMhI">My red text</p>
<p class="bold-2jIMhI blue-oVk-n1">My blue text</p>

Import styles from an external stylesheet

Alternatively, styles can be created into an external file and imported onto a svelte component from the <script> tag. The name referring to the import can then be used in the markup targetting any existing classname of the stylesheet.

Note: The import option is only meant for stylesheets relative to the component. You will have to set your own bundler in order to import node_modules packages css files.

/** style.css **/
.red { color: red; }
.blue { color: blue; }
<!-- Svelte component -->
<script>
  import style from './style.css';
</script>

<p class={style.red}>My red text</p>
<p class={style.blue}>My blue text</p>

Generated code

<style>
  .red-en-6pb { color: red; }
  .blue-oVk-n1 { color: blue; }
</style>

<p class="red-en-6pb">My red text</p>
<p class="blue-oVk-n1">My blue text</p>

Svelte scoped system on non class selectors

All existing rules of the imported stylesheet will apply to the component the same way <style> would do. All non class selectors will inherit the default svelte scoped system.

/** style.css **/
section { padding: 10px; }
p > strong { font-weight: 600; }
.red { color: red; }
<!-- Svelte component -->
<script>
  import style from './style.css';
</script>

<section>
  <p class={style.red}>My <strong>red</strong> text</p>
  <p>My <strong>blue</strong> text</p>
</section>

Generated code

<style>
  section.svelte-18te3n2 { padding: 10px; }
  p.svelte-18te3n2 > strong.svelte-18te3n2 { font-weight: 600; }
  .red-1sPexk { color: red; }
</style>

<section class="svelte-18te3n2">
  <p class="red-1sPexk svelte-18te3n2">My <strong class="svelte-18te3n2">red</strong> text</p>
  <p class="svelte-18te3n2">My <strong class="svelte-18te3n2">blue</strong> text</p>
</section>

Destructuring import

Only import the classnames you want to use as css modules. The rest of classes will fallback to the default svelte scoped system.

/** style.css **/
section { padding: 10px; }
.red { color: red; }
.blue { color: blue; }
.bold { font-weight: bold; }
<!-- Svelte component -->
<script>
  import { red, blue } from './style.css';
</script>

<section>
  <p class={red}>My <span class="bold">red</span> text</p>
  <p class="{blue} bold">My blue text</p>
</section>

Generated code

<style>
  section.svelte-18te3n2 { padding: 10px; }
  .red-1sPexk { color: red; }
  .blue-oVkn13 { color: blue; }
  .bold.svelte-18te3n2 { font-weight: bold; }
</style>

<section class="svelte-18te3n2">
  <p class="red-1sPexk">My <span class="bold svelte-18te3n2">red</span> text</p>
  <p class="blue-oVkn13 bold svelte-18te3n2">My blue text</p>
</section>

kebab-case situation

The kebab-case classnames are being transformed to a camelCase version on imports to facilitate their use on Markup and Javascript.

/** style.css **/
.success { color: green; }
.error-message {
  color: red;
  text-decoration: line-through;
}
<script>
  import css from './style.css';
</script>

<p class={css.success}>My success text</p>
<p class="{css.errorMessage}">My error message</p>

<!-- OR -->

<script>
  import { success, errorMessage } from './style.css';
</script>

<p class={success}>My success message</p>
<p class={errorMessage}>My error message</p>

Generated code

<style>
  .success-3BIYsG { color: green; }
  .error-message-16LSOn {
    color: red;
    text-decoration: line-through;
  }
</style>

<p class="success-3BIYsG">My success messge</p>
<p class="error-message-16LSOn">My error message</p>

Unnamed import

If a css file is being imported without a name, all rules will use the default svelte scoped system.

/** style.css **/
p { font-size: 18px; }
.success { color: green; }
<script>
  import './style.css'
</script>

<p class="success">My success message</p>
<p>My another message</p>

Generated code

<style>
  p.svelte-vg78j0 { font-size: 18px; }
  .success.svelte-vg78j0 { color: green; }
</style>

<p class="success svelte-vg78j0">My success messge</p>
<p class="svelte-vg78j0">My error message</p>

Directive and Dynamic class

Use the Svelte's builtin class: directive or javascript template to display a class dynamically.
Note: the shorthand directive is NOT working with CSS Modules.

<script>
  import { success, error } from './styles';

  let isSuccess = true;
  $: notice = isSuccess ? success : error;
</script>

<button on:click={() => isSuccess = !isSuccess}>Toggle</button>

<!-- Error -->
<p class:success>Success</p>

<!-- Ok -->
<p 
  class:success={isSuccess}
  class:error={!isSuccess}>Notice</p>

<p class={notice}>Notice</p>
<p class={isSuccess ? success : error}>Notice</p>

Configuration

Rollup

To be used with the plugin rollup-plugin-svelte.

import svelte from 'rollup-plugin-svelte';
import cssModules from 'svelte-preprocess-cssmodules';

export default {
  ...
  plugins: [
    svelte({
      preprocess: [
        cssModules(),
      ]
    }),
  ]
  ...
}

Webpack

To be used with the loader svelte-loader.

const cssModules = require('svelte-preprocess-cssmodules');

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.svelte$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'svelte-loader',
            options: {
              preprocess: [
                cssModules(),
              ]
            }
          }
        ]
      }
    ]
  }
  ...
}

Options

Pass an object of the following properties

Name Type Default Description
localIdentName {String} "[local]-[hash:base64:6]" A rule using any available token
includePaths {Array} [] (Any) An array of paths to be processed
getLocalIdent Function undefined Generate the classname by specifying a function instead of using the built-in interpolation
strict {Boolean} false When true, an exception is raised when a class is used while not being defined in <style>

localIdentName

Inspired by webpack interpolateName, here is the list of tokens:

  • [local] the targeted classname
  • [ext] the extension of the resource
  • [name] the basename of the resource
  • [path] the path of the resource
  • [folder] the folder the resource is in
  • [contenthash] or [hash] (they are the same) the hash of the resource content (by default it's the hex digest of the md4 hash)
  • [<hashType>:contenthash:<digestType>:<length>] optionally one can configure
    • other hashTypes, i. e. sha1, md4, md5, sha256, sha512
    • other digestTypes, i. e. hex, base26, base32, base36, base49, base52, base58, base62, base64
    • and length the length in chars

getLocalIdent

Customize the creation of the classname instead of relying on the built-in function.

function getLocalIdent(
  context: {
    context: string, // the context path
    resourcePath: string, // path + filename
  },
  localIdentName: {
    template: string, // the template rule
    interpolatedName: string, // the built-in generated classname
  },
  className: string, // the classname string
  content: { 
    markup: string, // the markup content
    style: string,  // the style content
  }
): string {
  return `your_generated_classname`;
}

Example of use

# Directory
SvelteApp
└─ src
   ├─ App.svelte
   └─ components
      └─ Button.svelte
<!-- Button.svelte -->
<button class="$style.red">Ok</button>

<style>
  .red { background-color: red; }
</style>
// Preprocess config
...
preprocess: [
  cssModules({
    localIdentName: '[path][name]__[local]',
    getLocalIdent: (context, { interpolatedName }) => {
      return interpolatedName.toLowerCase().replace('src_', '');
      // svelteapp_components_button__red;
    }
  })
],
...

Code Example

Rollup Config

export default {
  ...
  plugins: [
    svelte({
      preprocess: [
        cssModules({
          includePaths: ['src'],
          localIdentName: '[hash:base64:10]',
        }),
      ]
    }),
  ]
  ...
}

Svelte Component using <style>

<style>
  .modal {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 10;
    width: 400px;
    height: 80%;
    background-color: #fff;
    transform: translateX(-50%) translateY(-50%);
  }
  section {
    flex: 0 1 auto;
    flex-direction: column;
    display: flex;
    height: 100%;
  }
  header {
    padding: 1rem;
    border-bottom: 1px solid #d9d9d9;
  }
  .body {
    padding: 1rem;
    flex: 1 0 0;
  }
  footer {
    padding: 1rem;
    border-top: 1px solid #d9d9d9;
  }
  button {
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 5px;
  }
  .cancel {
    background-color: #f2f2f2;
  }
</style>

<div class="$style.modal">
  <section>
    <header>My Modal Title</header>
    <div class="$style.body">
      <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
    </div>
    <footer>
      <button>Ok</button>
      <button class="$style.cancel">Cancel</button>
    </footer>
  </section>
</div>

OR Svelte Component using import

/** style.css */
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 10;
  width: 400px;
  height: 80%;
  background-color: #fff;
  transform: translateX(-50%) translateY(-50%);
}

[...]
<script>
  import style from './style.css';
</script>

<div class={style.modal}>
  <section>
    <header>My Modal Title</header>
    <div class={style.body}>
      <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
    </div>
    <footer>
      <button>Ok</button>
      <button class={style.cancel}>Cancel</button>
    </footer>
  </section>
</div>

Final html code generated by svelte

<style>
  ._329TyLUs9c {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 10;
    width: 400px;
    height: 80%;
    background-color: #fff;
    transform: translateX(-50%) translateY(-50%);
  }
  section.svelte-1s2ez3w {
    flex: 0 1 auto;
    flex-direction: column;
    display: flex;
    height: 100%;
  }
  header.svelte-1s2ez3w {
    padding: 1rem;
    border-bottom: 1px solid #d9d9d9;
  }
  ._1HPUBXtzNG {
    padding: 1rem;
    flex: 1 0 0;
  }
  footer.svelte-1s2ez3w {
    padding: 1rem;
    border-top: 1px solid #d9d9d9;
  }
  button.svelte-1s2ez3w {
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 5px;
  }
  ._1xhJxRwWs7 {
    background-color: #f2f2f2;
  }
</style>

<div class="_329TyLUs9c">
  <section class="svelte-1s2ez3w">
    <header class="svelte-1s2ez3w">My Modal Title</header>
    <div class="_1HPUBXtzNG">
      <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
    </div>
    <footer class="svelte-1s2ez3w">
      <button class="svelte-1s2ez3w">Ok</button>
      <button class="_1xhJxRwWs7 svelte-1s2ez3w">Cancel</button>
    </footer>
  </section>
</div>

Note: The svelte scoped class is still being applied to the html elements with a style.

Why CSS Modules on Svelte

While the native CSS Scoped system should be largely enough to avoid class conflict, it could find its limit when working on a hybrid project. On a non full Svelte application, the thought on the class naming would not be less different than what we would do on a regular html page. For example, on the modal component above, It would have been wiser to namespace some of the classes such as .modal-body and .modal-cancel in order to prevent inheriting styles from other .body and .cancel classes.

License

MIT

About

Svelte preprocessor to generate CSS Modules classname on Svelte components

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 50.9%
  • TypeScript 41.8%
  • Svelte 6.2%
  • CSS 1.1%