Bootstrap의 Scoped CSS 문제 해결 방법

2018. 11. 16. 22:38서버 프로그래밍

부트스트랩을 사용한 Vue.js 컴포넌트와 부트스트랩을 사용하지 않은 Vue.js 컴포넌트를 하나의 Vue.js 프로젝트 통합을 하려다 보니 이런 저런 문제가 발생한다.


1. Vue.js를 Typescript로 만들어진 기존 프로젝트가 Router가 없다.

다른 언어도 마찬가지이지만, Javascript 역시 어떤 프레임워크를 사용하고 어떤 템플릿을 이용하여 프로젝트가 만들어져서 개발되었느야에 따라서 천차만별의 소스 구조가 만들어질 수 있다. 노멀한 Vue.js 프로젝트에 Router를 추가하는 것은 일도 아니지만, Typescript를 이용해서 만들어진 Vue.js 프로젝트에 Router를 추가하는 것이 뭔가 잘 안되는 문제가 있었다. 다양한 삽질 끝에, 다행히 다음 예제 소스를 알게 되었고 App.vue, main.ts, router.ts 등을 참고해서 적용해보니 아주 잘 동작한다.

https://github.com/Jack85/vue-ts-ex


2. 기존의 컴포넌트 수정을 최소화해서 부트스트랩을 사용한 컴포넌트를 추가하고자 한다.

부트스트랩을 사용하는 이유는 최소의 시간을 들여서 신속하게 원하는 결과를 만들기 위해서이다. 하지만, 이런류의 무거운 CSS 프레임워크를 싫어하는 사람도 있기 때문에 모든 컨트롤러에서 사용하기보다는 필요한 컨트롤러에서만 사용하도록, 부트스트랩을 Scoped CSS로 포함시켰다. 

https://vue-loader.vuejs.org/guide/scoped-css.html#mixing-local-and-global-styles

Mixing Local and Global Styles

You can include both scoped and non-scoped styles in the same component:

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
</style>

대부분은 정상적으로 동작하는 것 같은데, 모달이나 Alert 등과 같은 일부 기능에서는 정상 동작이 되지 않는다. Vue.js용 Bootstrap 레퍼런스 내용을 읽다보니 다음과 같은 항목이 보인다. 일부 기능이 scoped CSS로 정의되었을 때 동작하지 않는다는 것이 아닌가. 아마도 모달 역시 동일한 문제인 것 같다.

https://bootstrap-vue.js.org/docs/components/table/

Notes:

  • Field properties, if not present, default to null (falsey) unless otherwise stated above.
  • thClass and tdClass will not work with classes that are defined in scoped CSS
  • For information on the syntax supported by thStyle, see Class and Style Bindings in the Vue.js guide.
  • Any additional properties added to the field objects will be left intact - so you can access them via the named scoped slots for custom data, header, and footer rendering.


한참을 삽질을 하다가, 결정적인 단서를 찾았다. Deep Selector와 부트스트랩의 SCSS를 이용하면 해결할 수 있다는 것이다. 테스트 해보았더니 확실하게 동작한다. 일부는 여전히 완벽하지 않지만, 어느 정도를 동작을 한다.

https://stackoverflow.com/questions/49653931/scope-bootstrap-css-in-vue

With scoped, the parent component's styles will not leak into child components.

If you want a selector in scoped styles to be "deep", i.e. affecting child components, you can use the >>> combinator

from the Vue doc for scoped CSS

The modal you mentioned is apparently not being controlled by the component where you imported bootstrap. Perhaps it's a child component. Perhaps you're using the jquery version of Bootstrap modal. Either way, the data attributes won't be added to the modal.

In order to solve this, you need Deep Selector. (you may read about it in more detail in https://vue-loader.vuejs.org/en/features/scoped-css.html)

Here's how I would import the entire Bootstrap CSS using SCSS. (I think it's impossible to do this using pure CSS only.)

<template>
  <div class="main-wrapper">
    /* ... */
  </div>
</template>

<style scoped lang="scss">
.main-wrapper /deep/ {
  @import "~bootstrap/dist/css/bootstrap.min";
}
</style>


참고용으로 동적 컨텐츠에 Scoped CSS가 적용 안되는 부분에 대한 스레드도 있다.

https://github.com/vuejs/vue-loader/issues/559


3. 잘 사용하던 fontawesome 라이브러리에서 빌드 시 에러가 난다.

Could not find a declaration file for module '@fortawesome/vue-fontawesome'. '........../node_modules/@fortawesome/vue-fontawesome/index.js' implicitly has an 'any' type.

Try `npm install @types/fortawesome__vue-fontawesome` if it exists or add a new declaration (.d.ts) file containing `declare module 'fortawesome__vue-fontawesome'

아무래도 TypeScript라서 발생하는 오류라고 보인다. 원래는 main.ts에 넣으려고 했으나, 오류가 발생해서 다음과 같이 부트스트랩을 사용하는 Vue에 직접 넣어주고 마무리했다.


https://github.com/FortAwesome/vue-fontawesome/issues/24

file: SampleComponent.vue

import { library } from '@fortawesome/fontawesome-svg-core'
import { faCoffee } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faCoffee)

@Component({
   name: SampleComponent,
   components: {
        FontAwesomeIcon,
    },
})