지난번에 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. 버튼 클릭하면 데이터를 업데이트되고 그래프가 자동적으로 바뀐다.

vue plotly

Plotly.vue 파일을 보면

    this.$watch('data', () => {
      this.internalLayout.datarevision++
      this.react()
    }, { deep: !this.watchShallow })

data 를 watch 하고 있다가 react 를 부르는구나… SVELTE 에서 $: 를 쓰면 되지 않을까?

svelte wrapper 에서 할 수 있는 일을 보면,

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

출처 : https://stackoverflow.com/questions/35560305/d3-js-uncaught-typeerror-cannot-read-property-document-of-undefined

“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 는 매우 큰 라이브러리이다. 하지만 우리는 그래프를 그리기만 할 것이므로 plotly.js 분석은 여기까지 하도록 한다.

Wrapper 구현

// 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 차이, 배포시 문제 등을 알아보도록 하겠다.