미니앱 마크업, 스타일링, 그리고 스크립팅
마크업 언어 #
전에 이야기한 것처럼 미니앱은 순수 HTML이 아닌 HTML의 방언을 사용해요. Vue.js의 Text Interpolation과 Directive를 사용해본 경험이 있다면 익숙하겠지만, 비슷한 개념들이 XML Transformations (XSLT) 등의 형태로 존재했었어요. 아래는 WeChat의 WXML의 코드 예제들이에요. 하지만 개념 자체는 Alipay의 AXML, Baidu의 Swan Element, ByteDance의 TTML, (개발자 도구에서는 Bxml이라고 불리지만요), 그리고 QuickApp의 HTML 등 모든 미니앱 플랫폼들이 비슷해요. Vue.js처럼, 미니앱의 기반 프로그래밍 콘셉트는 model-view-viewmodel (MVVM)이에요.
데이터 바인딩 #
데이터 바인딩은 Vue.js의 Text Interpolation과 비슷해요.
<!-- wxml -->
<view>{{message}}</view>
// page.js
Page({
data: {
message: 'Hello World!',
},
});
리스트 렌더링 #
리스트 렌더링은 Vue.js의 v-for
directive와 비슷해요.
<!-- wxml -->
<view wx:for="{{array}}"
>{{item}}</view
>
// page.js
Page({
data: {
array: [1, 2, 3, 4, 5],
},
});
조건부 렌더링 #
조건부 렌더링은 Vue.js의 v-if
directive와 비슷해요.
<!-- wxml -->
<view wx:if="{{view == 'one'}}">One</view>
<view wx:elif="{{view == 'two'}}">Two</view>
<view wx:else="{{view == 'three'}}">Three</view>
// page.js
Page({
data: {
view: 'three',
},
});
템플릿 #
HTML 템플릿의 content
복제를 의무화하는 것이 아니라, WXML 템플릿들은 템플릿 정의와 링크된 is
속성을 통해 선언적으로 사용될 수 있어요.
<!-- wxml -->
<template name="person">
<view>
First Name: {{firstName}}, Last Name: {{lastName}}
</view>
</template>
<template is="person" data="{{...personA}}"></template>
<template is="person" data="{{...personB}}"></template>
<template is="person" data="{{...personC}}"></template>
// page.js
Page({
data: {
personA: {firstName: 'Alice', lastName: 'Foo'},
personB: {firstName: 'Bob', lastName: 'Bar'},
personC: {firstName: 'Charly', lastName: 'Baz'},
},
});
스타일링 #
스타일링도 CSS의 방언으로 할 수 있어요. WeChat은 이름을 WXSS라고 해요. Alipay의 경우 ACSS라고 하고, Baidu는 직관적으로 CSS라고 하고 ByteDance 방언은 TTSS라고 해요. 이들의 공통점은 CSS를 반응형 픽셀 단위로 확장한다는거예요. 일반적인 CSS로 작성할 경우, 개발자는 모든 픽셀 단위를 서로 다른 화면 비율과 픽셀 비율에 맞춰 변환해야 해요. TTSS의 경우 rpx
단위를 지원해, 화면 크기를 750rpx
로 잡아서 미니앱에서 그 모든 변환을 담당해요. 예를 들어, Pixel 3a 휴대전화의 경우는 화면 폭이 393px
이며, 화면비는 2.75
예요. 즉, 반응형 200rpx
는 104px
이 돼요. 개발자 도구를 통해서 393px / 750rpx * 200rpx ≈ 104px임을 확인할 수 있어요. 안드로이드에서는 동일한 개념을 density-independent pixel이라고 해요.

/* app.wxss */
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0; /* ← responsive pixels */
box-sizing: border-box;
}
컴포넌트들이 Shadow DOM을 사용하기 않기 때문에, 한 페이지 내에서 선언된 스타일은 모든 컴포넌트에 적용돼요. 대부분 글로벌 스타일을 담당하는 Root Stylesheet이 하나 존재하고, 각 페이지마다 Stylesheet가 별도로 존재해요. 스타일은 @import
구문을 통해서 불러올 수 있어요 (마치 웹 CSS의 @import
처럼요). HTML처럼 스타일은 in-line으로 선언될 수도 있고, 데이터 바인딩을 통한 동적 Text Interpolation으로도 적용할 수 있어요.
<view style="color:{{color}};" />
스크립팅 #
미니앱은 JavaScript의 "안전한 부분집합"과 CommonJS 또는 RequireJS와 유사한 모듈 시스템을 지원해요. JavaScript 코드는 eval()
등으로 실행될 수 없고 어떤 함수도 new Function()
을 통해서 생성될 수 없어요. 실행 환경은 기기에서는 V8이나 JavaScriptCore이고, 시뮬레이터에서는 V8 또는 NW.js이에요 대부분 IDE에서 트랜스파일링을 지원하기 때문에 빌드 타겟이 오래된 WebView 구현일지라도 ES6 등의 새로운 문법을 사용하는 것도 가능해요. (추가 정보) 슈퍼앱의 개발자 문서에는 스크립팅 언어들이 JavaScript가 아니라고 명시하고 있어요. 하지만 이 이야기는 거의 모듈 시스템에 대해서만 해당되는 이야기예요. 그 말은, 미니앱은 아직 표준 ES Modules을 지원하지 않아요.
이전에 언급한 것처럼 미니앱의 프로그래밍 콘셉트는 model-view-viewmodel (MVVM)이에요. 논리 계층(Logic Layer)과 화면 계층(View Layer)는 서로 다른 쓰레드에서 동작해요. 덕분에 계산에 오래 걸리는 작업 중에서 사용자 화면은 멈추지 않아요. 웹으로 비유하자면, 스크립트들이 Web Worker 상에서 돌아가는거예요.
WeChat의 스크립팅 언어는 WXS라고 하고, Alipay는 SJS, ByteDance 또한 SJS이라고 해요.
Baidu는 자신의 언어를 JS라고 해요. 이 스크립트들은 실행되기 위해서는 특수한 태그가 포함되어야 해요. 예를 들어 WeChat의 경우는 <wxs>
가 포함되어야 해요. 이에 대비해서 QuickApp은 표준 <script>
태그와 ES6 JS 문법을 사용해요.
<wxs module="m1"> var msg = "hello world"; module.exports.message = msg; </wxs>
<view>{{m1.message}}</view>
모듈은 src
요소나 require()
을 통해서도 불러올 수 있어요.
// /pages/tools.wxs
var foo = "'hello world' from tools.wxs";
var bar = function (d) {
return d;
};
module.exports = {
FOO: foo,
bar: bar,
};
module.exports.msg = 'some msg';
<!-- page/index/index.wxml -->
<wxs src="./../tools.wxs" module="tools" />
<view>{{tools.msg}}</view>
<view>{{tools.bar(tools.FOO)}}</view>
// /pages/logic.wxs
var tools = require('./tools.wxs');
console.log(tools.FOO);
console.log(tools.bar('logic.wxs'));
console.log(tools.msg);
JavaScript 브릿지 API #
JavaScript 브릿지는 미니앱들을 강력한 기기 기능들을 사용할 수 있도록 해요. (강력한 기능에 접근 참고) 그 외에도 여러 편리한 기능들을 제공해요. 이에 대한 개략적인 이해를 위해 WeChat, Alipay, Baidu, ByteDance, 그리고 Quick App 등 서로 다른 API들을 확인해보세요.
기능 감지 또한 아주 직관적이에요. 모든 플랫폼에서 말 그대로 canIUse()
함수를 지원하기 때문이에요. 아마도 웹사이트 caniuse.com에서 영감을 받은 것 같아요. 예를 들어, ByteDance의 tt.canIUse()
은 API, 함수, 인자, 옵션, 컴포넌트, 그리고 속성 등을 검사할 수 있도록 해요.
// Testing if the `<swiper>` component is supported.
tt.canIUse('swiper');
// Testing if a particular field is supported.
tt.canIUse('request.success.data');
감사의 말 #
이 글은 Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent, 그리고 Keith Gu에 의해 리뷰되었어요.