지난번에 SVELTE plotly.js 가 없어서 프로젝트 마이그레이션이 지연되었다.
그래서 SVELTE 를 조금만 더 파 보기로 했다.
SVELTE 로 개발 프로젝트를 하는 것은 없어서, 이미 만들어놓은 React PID controller 를 SVELTE 로 바꿔보기로 했다. 제어 로직은 이미 클래스로 분리해 놓았기 때문에 UI 부분만 마이그레이션 하면 되었다.
그래프를 그리는데 plotly.js 와 react-plotly.js 를 사용했는데, 이 부분을 SVELTE 에 맞춰보는 것이 가장 필요했던 작업이었다. react-plotly.js 는 plotly.js react 인터페이스로 포장한 wrapper 패키지이다. SVELTE 에서도 마찬가지로 wrapper 패키지가 있으려니 했지만 찾지 못했다. 하나 만들어 보자.
첫 발을 내 딛기
Vue Plotly
문법적으로 볼 때, SVELTE 는 react 보다 vue 에 가깝다. Vue 에서는 이미 만들어 놓은 plotly.js wrapper 가 있는데, 이것들을 참고하기로 했다. github.com:statnett/vue-plotly.git 이 가장 간단(?)해 보였다.
“vue 도 배워야 하나” 하는 자괴감이 들었지만, 기본적으로 Vue 를 조금 알아야 하기 때문에… 몇 시간 뒤적뒤적 Vue 에 대해 필요한만큼 학습
Vue 하위 컴포넌트로 prop 넘기는 방법을 이해하고, Plot 그래프를 그리기도 OK. 버튼 클릭하면 데이터를 업데이트되고 그래프가 자동적으로 바뀐다.

Plotly.vue 파일을 보면
this.$watch('data', () => {
this.internalLayout.datarevision++
this.react()
}, { deep: !this.watchShallow })
data 를 watch 하고 있다가 react 를 부르는구나… SVELTE 에서 $: 를 쓰면 되지 않을까?
svelte wrapper 에서 할 수 있는 일을 보면,
- plot 그리기
- plot 에서 발생하는 이벤트 넘겨주기
- 이미지로 저장
일단 Plot 그리기만 잘 하면 일단 충분할 것 같다. 목표가 정해졌다.
프로젝트 생성부터
plotly-test-svelte 라는 SVELTE 프로젝트르 하나 만들어서, 위에서 한 것과 동일한 것을 만들어 보려고 한다.
$ npx degit sveltejs/template plotly-test-svelte
$ cd plotly-test-svelte
$ yarn
$ yarn add plotly.js
$ yarn dev
HELLO WORLD! 가 잘 뜬다.
plotly.js wrapper 인 Plotly.svelte 를 만들 것이다.
Wrapper 를 만들고,
// vi src/Plotly.svelte
<script>
import Plotly from 'plotly.js'
</script>
App 에서 이를 가져다 쓸 것이다.
// vi src/App.svelte
<script>
import Plot from './Plotly.svelte';
export let name;
</script>
빌드가 좀 오래걸린다… 설마… 엄청난 에러가 떨어진다.
Package.json
plotly.js 의 package.json 에는 main 과 webpack 두개의 속성이 보인다.
$ vi node_modules/plotly.js/package.json
...
"main": "./lib/index.js",
"webpack": "./dist/plotly.js",
main 은 import 할때 default 시작 위치다. webpack 속성에 있는 소스로 바꿔 본다. (dist 는 보통 컴파일 된 결과물을 베포하기 위한 공간이다)
$ vi src/Plotly.svelte
<script>
import Plotly from "plotly.js/dist/plotly.js"
</script>
이제 빌드 에러는 사라졌다.
이제 yarn dev 로 다시 실행시켜보는데, 브라우저에 아무것도 안나온다. 디버그 창을 열어보자.
plotly.js:26368 Uncaught TypeError: Cannot read property 'document' of undefined
at plotly.js:26368
...
변역: node_modules/plotly.js/dist/plotly.js 26368 라인에서 정의되지 않은 객체에서 document 속성을 읽으려고 한다.
소스를 보면 다음과 같다.
var d3_document = this.document;
plotly.js 는 내부적으로 d3 라이브러리를 이용한다는 사실을 알게 되었다. 아… this 가 undefined 구나. 어쩌라고???
다시 만난 this
피식 웃음이 나왔다. 바닐라 자바스크립트를 쓰는 것도 아닌데, 아직도 this 관련 에러가 나온다고…
메러 메시지는 구글신에게 물어본다.
검색 : cannot read property ‘document’ of undefined d3
It sounds like something (Babel, most likely) is inserting “use strict”; at the beginning of the D3 script file or combining it into another file with “use strict” at the top. That means this at global scope (or in a function called without a specific this) is no longer a reference to the global object, it’s undefined. (Whereas in “loose” mode or in a function called with no specific this value, this at global scope is a reference to the global object, which is also accessible via the global variable `window1.)
“use strict” 를 꺼야 한다는 이야기가 아닐까? 그런데 어떻게?
use strict
잠깐이라도 어떤 문제인지 이해해보자.
아래 html 을 브라우저에서 읽으면, 1, 2, 3 모두 window 객체를 리턴한다. 첫번째 “use strict” 주석을 해재하면, 2, 3 은 undefined 가 뜬다. 이것이 global scope is no longer a reference… 뜻인가 보다.
<script>
console.log("1", this)
var app = (function () {
// "use strict"
console.log("2", this);
!function() {
// "use strict"
console.log("3", this);
}()
}())
</script>
<h1> Loot at this </h1>
바로 1번 위치에서 “use strict” 를 넣으면 this.document 에서 this 가 undefined 가 되는 것이다.
rollup.config.js 수정
rollup 다음 문서를 참고한다. https://rollupjs.org/guide/en/#outputstrict
output 옵션에 strict: false 를 추가한다.
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
strict: false,
name: 'app',
file: 'public/build/bundle.js'
},
이제 d3 관련 에러도 사라졌다.
SVELTE 공식 지원 번들러는 rollup 과 webpack 이 있다. webpack 을 쓰려면, 프로젝트 template 를 바꿔야 한다.
$ npx degit sveltejs/template-webpack plotly-test-webpack
이 경우는 use strict 문제가 생기지 않는다.
Plotly.svelte 만들기
구현은 의외로 간단하다.
plotly.js 동작 이해
- Plotly.js 에서 그래프를 그리려면 data, layout, config 세가지 파라미터가 필요하다.
- Plotly.svelte 는 이 파라미터를 받아서 이를 파라미터로 Plotly.js 의 함수를 호출하는 구조로 되어있다.
- Plotly.newPlot 으로 그래프를 새로 그리고, data 가 바뀌어 그래프를 업데이트 할 때는 Plotly.react 를 호출하는 것 같다.
- App.js 에서 이 세가지를 plotly.js 가 원하는 형태로 넘겨주면 된다.
plotly.js 는 매우 큰 라이브러리이다. 하지만 우리는 그래프를 그리기만 할 것이므로 plotly.js 분석은 여기까지 하도록 한다.
Wrapper 구현
- export let 으로 property 3개를 받는다.
- 컴포넌트가 mount 될 때 그래프를 그릴 영역을 얻어와서 그래프를 그린다.
- data 가 업데이트 되면 $: reativity 가 자동적으로 호출된다.
- 일단 그래프 그래는 기능만 갖고 있다. Plotly.js 로부터 발생되는 이벤트 처리나,이미지 저장 같은 작업은 당장 필요하지 않다.
// vi src/Plotly.svelte
<script>
import Plotly from 'plotly.js/dist/plotly';
import { onMount } from 'svelte';
export let data;
export let layout;
export let config;
let div_for_plot = undefined
let Plot = undefined
onMount(() => {
div_for_plot = document.getElementById('plot_svelte');
Plot = new Plotly.newPlot(div_for_plot, data, layout, config);
});
$: if (Plot) Plotly.react(div_for_plot, data, layout, config);
</script>
<div id="plot_svelte" />
결론
제주도 한달 살기처럼 SVELTE 일주일만 더 써보기로 했는데 좀 더 오랫동안 SVLTE 를 가지고 다뤄봤다.
import 문제, use strict 문제 등은 레거시 코드를 다루는데 있어서 많이 발생하는 문제였는데, this 를 만나게 될 줄은 몰랐다.
다음에는 빌드 성능, rollup 과 webpack 차이, 배포시 문제 등을 알아보도록 하겠다.