Nuxt.js で Sass ( Scss ) 変数を TypeScript ( JavaScript ) からも使いたい

[Nuxt.js](https://nuxtjs.org/ja/) で開発していると、Sass ( Scss ) 変数を管理するファイルを作成し、[@nuxtjs/style-resources](https://www.npmjs.com/package/@nuxtjs/style-resources) ライブラリを使って、各コンポーネントから参照することが多いかと思います。

``` scss
// @/assets/scss/_consts.scss

$COLOR_PRIMARY: #4aac00;
$COLOR_SECONDARY: #0f83fd;
```

``` vue
<!-- @/components/Test.vue -->

<template>
  <div class="Test">
    <p class="Test_Text">Text1</p>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  name: 'Test'
})
</script>

<style lang="scss" scoped>
  .Test_Text {
    color: $COLOR_PRIMARY;
  }
</style>
```

こんな感じで。

**この Sass ( Scss ) 変数を Vue コンポーネントの `<template>` 内や、`<script>` 内でも使いたい！**
( JavaScript と CSS で変数を共有したい！ )

## 手順

### １、`:export` を定義する

``` scss
// @/assets/scss/_consts.scss

$COLOR_PRIMARY: #4aac00;
$COLOR_SECONDARY: #0f83fd;

+ :export {
+   COLOR_PRIMARY: $COLOR_PRIMARY;
+   COLOR_SECONDARY: $COLOR_SECONDARY
+ }
```

`:export` で Vue コンポーネントから参照したい変数を定義する。

### ２、Vue コンポーネントで使う

``` vue
<!-- @/components/Test.vue -->

<template>
  <div class="Test">
    <p class="Test_Text">Text1</p>
+   <p :style="styleObj">Text2</p>
+   <p :style="{ color: COLOR_SECONDARY }">Text3</p>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
+ import colorModule from '@/assets/scss/_consts.scss';
export default Vue.extend({
+ name: 'Test', // ←カンマ追加しただけ
+ data () {
+   return {
+     COLOR_SECONDARY: colorModule.COLOR_SECONDARY
+   }
+ },
+ computed: {
+   styleObj () {
+     return {
+       color: this.COLOR_SECONDARY
+     }
+   }
+ }
})
</script>

<style lang="scss" scoped>
  .Test_Text {
    color: $COLOR_PRIMARY;
  }
</style>
```

`import colorModule from '@/assets/scss/_consts.scss';` で scss ファイルをインポートします。
そして、`colorModule.COLOR_PRIMARY` や `colorModule.COLOR_SECONDARY` とすることで、先ほど `:export {}` 内に定義した変数にアクセスできます。

### ３、エラーの解消

#### stylelint を使っている場合

[`stylelint`](https://stylelint.io/) を使っているプロジェクトであれば、このようなエラーが出ているのではないでしょうか？

```
assets/scss/_consts.scss
 5:1  ✖  Unexpected unknown pseudo-class selector ":export"   selector-pseudo-class-no-unknown
 6:3  ✖  Unexpected unknown property "COLOR_PRIMARY"          property-no-unknown             
 7:3  ✖  Unexpected unknown property "COLOR_SECONDARY"        property-no-unknown
```

これを解消するために、ルールを追加します。

``` javascript
// stylelint.config.js

module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-prettier'
  ],
  rules: {
    'selector-pseudo-class-no-unknown': [
      true,
      { 'ignorePseudoClasses': ['export'] }
    ],
    'property-no-unknown': [
      true,
      { 'ignoreProperties': ['/^COLOR_/'] }
    ]
  }
}
```

- [`selector-pseudo-class-no-unknown`](https://stylelint.io/user-guide/rules/list/selector-pseudo-class-no-unknown/) に、`:export` という疑似クラスの使用を許可するルールを追加
- [`property-no-unknown`](https://stylelint.io/user-guide/rules/list/property-no-unknown/) に、正規表現で `COLOR_` から始まるプロパティの使用を許可するルールを追加

#### TypeScript を使っている場合

[`TypeScript`](https://typescript.nuxtjs.org/ja/) を使っているプロジェクトであれば、このようなエラーが出ているのではないでしょうか？

```
 ERROR  ERROR in components/Test.vue:12:25                                                                                                                                                        12:35:18
TS2307: Cannot find module '@/assets/scss/_consts.scss' or its corresponding type declarations.
    10 | <script lang="ts">
    11 | import Vue from 'vue'
  > 12 | import colorModule from '@/assets/scss/_consts.scss';
       |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    13 | export default Vue.extend({
    14 |   name: 'Test',
    15 |   data () {
```

これを解消するために、型定義ファイルを作成します。

```
// @/types/scss.d.ts

declare module '*.scss';
```

## 番外編

ここまでで、Sass ( Scss ) 変数が vue ファイルの　`<template>` 内や `<script>` 内で使えるようになったかと思います。

が、`:export {}` を定義したファイル内で [Sass の math 機能](https://sass-lang.com/documentation/modules/math) を使ったときに、このようなエラーが出ているのではないでしょうか？

```
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):                                                                                                                friendly-errors 12:43:45
SassError: @use rules must be written before any other rules.
   ╷
14 │ @use "sass:math";
   │ ^^^^^^^^^^^^^^^^
   ╵
```

これを解消するために、`export: {}` 部分のみを別ファイルに切り出します。

``` scss
// @/assets/scss/constsForJs.scss

:export {
  COLOR_PRIMARY: $COLOR_PRIMARY;
  COLOR_SECONDARY: $COLOR_SECONDARY
}
```

``` vue
<!-- @/components/Test.vue -->

<template>
  <div class="Test">
    <p class="Test_Text">Text1</p>
    <p :style="styleObj">Text2</p>
    <p :style="{ color: COLOR_SECONDARY }">Text3</p>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
+ import colorModule from '@/assets/scss/constsForJs.scss';
export default Vue.extend({
  name: 'Test',
  data () {
    return {
      COLOR_SECONDARY: colorModule.COLOR_SECONDARY
    }
  },
  computed: {
    styleObj () {
      return {
        color: this.COLOR_SECONDARY
      }
    }
  }
})
</script>

<style lang="scss" scoped>
  .Test_Text {
    color: $COLOR_PRIMARY;
  }
</style>
```

## まとめ

今回は、[Nuxt.js](https://nuxtjs.org/) ( [Vue.js](https://jp.vuejs.org/index.html) ) を例に挙げましたが、[Next.js](https://nextjs.org/) ( [React](https://ja.reactjs.org/) ) や、UI ライブラリを使っていない場合でも同じ手法が使えます。

`:export` で 変数を定義し、sass ( scss ) ファイルを `import` する。
そして、必要であれば `stylelint` にルールを追加や型定義ファイルを追加するだけ。