【Svelte入門】Svelteの開発環境をDocker Alpine内で簡単に構築する


2022/01/17
2022/01/19
蛸壺の技術ブログ|Svelteの開発環境をDocker Alpine内で簡単に構築する

Svelteは非常に軽快なWebアプリケーションを作るのに向いているJavascriptフレームワークです。

弊社の業務でもあれやこれやとAngularを使うことが多かったのですが、簡単なJSアプリなど何でもかんでもAngularで開発しようとすると、
Hello World程度のプログラムを作るのにも大量のライブラリやランタイムを読み込む必要があり、あれよあれよとプロジェクトの容量が数百MB以上に膨れ上がるし、ビルドも重いことに悩んでいました。

そこで小さい規模のアプリケーションをSvelteで開発することに切り替えると、プロジェクトサイズもビルドサイズも数十MBに圧縮しできて、ビルド時間もかなり短縮できるので、最近ではかなり重宝しています。

今回は、Docker AlpineベースのNodeコンテナから基本的なSvelteプロジェクトをビルドする環境をさくっと作るやり方をまとめて紹介していきます。


Alpine DockerコンテナでSvelteの開発環境を整える

まずは適当なプロジェクトフォルダを作成し、そのフォルダ内で作業し始めます。

そこに空の
Dockerfiledocker-compose.ymlを新規作成しておきます。

            
            $ touch Dockerfile docker-compose.yml
$ tree
.
├── Dockerfile
└── docker-compose.yml
        
AlpineベースのDockerコンテナで開発を行う場合、node.jsが動けば十分Svelteも動作するはずです。

そこで
node公式のDockerイメージを利用します。

            
            FROM node:17-alpine

RUN apk update && apk upgrade && \
    apk add --no-cache bash openssh

CMD ["bash"]
        
今回はとりあえずタグが17-alpineのDockerイメージをベースにして、拡張したイメージを使いますが、bashが使えるくらいにしかカスタマイズしていません。

他に必要なコマンドといえば、後述しますがSvelteにtypescriptを導入するスクリプトをwgetでダウンロードするのに使うくらいで、最低限Svelte自体を利用する分には、nodeがインストール出来ていれば十分です。

なお、nodeのバージョンはv12以上なら問題なく動作しておりますが、ここでは最新のv17を利用しました。

重要なのはdocker-compose.ymlの方で、

            
            version: '3'

services:
  app:
    image: svelte_dev:17-alpine
    build: .
    user: "node:node"
    container_name: svelte_dev
    environment:
      NODE_ENV: "development"
    volumes:
      - ./:/usr/src/app
    working_dir: "/usr/src/app"
    ports:
      - "4200:4200"
    tty: true
        
としておきます。

DockerコンテナのDEVサーバーモードから起動する際には、
tty: trueにしておかないとホスト側のブラウザからアクセス出来ないのでお忘れなく。

ファイルを編集したら、このイメージをローカルビルドしておきます。

            
            $ docker-compose build
        
上手くカスタムDockerイメージがビルド出来たら、一旦インタラクティブモードに入れるか試しておきます。

            
            $ docker-compose up -d && docker-compose exec app bash
#コンテナのインタラクティブモード内...
        
コンテナから出る時は、exitとタイプするか、キーボードからCTRL + Dで抜けられます。

その後、起動したコンテナを落とす際には、downサブコマンドを使います。

            
            $ docker-compose down
        

Svelteプロジェクトを初期化する

先程の話まででSvelteアプリ開発用Dockerコンテナが出来ましたので、ここからはそのコンテナ内にインタラクティブモードで入ってからの作業ということで話を進めていきます。

なお以降ではSvelte公式の
初めての人向けSvelteアプリテンプレートを参考に、Svelteプロジェクトの理解を深めるため手動で一つづつコードを作成していきます。

Svelteの基本プロジェクトを構築する

現在の作業ルートにsvelte-appという名前のフォルダを作り、その中で作業します。

            
            $ mkdir svelte-app && cd svelte-app
        
そこからnpm initしても良いですが、ただのpackage.jsonを作るだけなので、以下のファイルを生成します。

            
            {
  "name": "svelte-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public --host 0.0.0.0 --port 4200"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^17.0.0",
    "@rollup/plugin-node-resolve": "^11.0.0",
    "rollup": "^2.3.4",
    "rollup-plugin-css-only": "^3.1.0",
    "rollup-plugin-livereload": "^2.0.0",
    "rollup-plugin-svelte": "^7.0.0",
    "rollup-plugin-terser": "^7.0.0",
    "svelte": "^3.0.0"
  },
  "dependencies": {
    "sirv-cli": "^2.0.0"
  }
}
        
各パッケージは執筆時点での最新バージョンを指定していますが、より新鮮なバージョンのパッケージを入れ直したい場合には、各パッケージのバージョニング指定を手動で変更してもらうか、yarn add <パッケージ名>で導入しなおしてみてください。

package.jsonファイルが追加できたら、パッケージインストールします。

            
            $ yarn install
        
次に、rollup.config.jsという名前で、以下の名前のJavascriptコードを追加します。

            
            import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';

const production = !process.env.ROLLUP_WATCH;

function serve() {
    let server;

    function toExit() {
        if (server) server.kill(0);
    }

    return {
        writeBundle() {
            if (server) return;
            server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
                stdio: ['ignore', 'inherit', 'inherit'],
                shell: true
            });

            process.on('SIGTERM', toExit);
            process.on('exit', toExit);
        }
    };
}

export default {
    input: 'src/main.js',
    output: {
        sourcemap: !production,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte({
            compilerOptions: {
                dev: !production
            }
        }),
        css({ output: 'bundle.css' }),
        resolve({
            browser: true,
            dedupe: ['svelte']
        }),
        commonjs(),
        !production && serve(),
        !production && livereload('public'),
        production && terser()
    ],
    watch: {
        clearScreen: false
    }
};
        
このRollupがSvelteの標準バンドラーになり、さまざまな設定をこのファイルで一括して行うことでアプリ開発を進めていくことになります。

まだここでは細かい設定の意味は理解できなくても結構です。

とりあえず先に進みます。

リソース用のフォルダとして、
srcフォルダを作成し、そこにmain.jsApp.svelteの空のファイルを新規作成します。

            
            $ mkdir src && cd src
$ touch main.js App.svelte
        
main.jsが今回のSvelteアプリのエンドポイントなるメインファイルで、以下のような内容にします。

            
            import App from './App.svelte';

const app = new App({
    target: document.body,
    props: { name: 'world' }
});

export default app;
        
アプリのルートコンポーネントであるApp.svelteファイルは以下のようにします。

            
            <script>
    export let name;
</script>

<main>
    <h1>Hello {name}!</h1>
    <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }
    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }
    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }
</style>
        

次に再びプロジェクトルート(
svelte-appフォルダ)に戻って、今度はpublicと言う名前で、共通のアセット置き場用のフォルダを作成しておきます。

この中に、Webアプリとしてレンダリングする
index.htmlを作ります。

            
            $ cd ../
$ mkdir public && cd public
$ touch index.html
        
ではindex.htmlの中身を以下のように編集しましょう。

            
            <!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width,initial-scale=1'>
        <title>Svelte app</title>
        <link rel='icon' type='image/png' href='/favicon.png'>
        <link rel='stylesheet' href='/build/bundle.css'>
        <script defer src='/build/bundle.js'></script>
    </head>
    <body></body>
</html>
        
ちなみにfavicon用の画像が無くてもアプリ的には支障は無く動くのですが、内部で404のレスポンスコードエラーが発生するのが気持ち悪いので、公式の画像を拝借して、publicフォルダに入れておきます。

直接画像をダウンロードするか、wgetでファイルを取得するかで画像をフォルダに追加しておきます。

            
            $ wget https://raw.githubusercontent.com/sveltejs/template/master/public/favicon.png
        
ここまでで、アプリを構成する必要最低限のリソースの準備が完了しました。

ここまででプロジェクトルート(ここでは
svelte-appフォルダ)が以下のようなファイル構造になっていたらOKです。

            
            $ tree
.
├── node_modules
├── package.json
├── public
│   ├── favicon.png
│   └── index.html
├── rollup.config.js
├── src
│   ├── App.svelte
│   └── main.js
└── yarn.lock
        

Svelteアプリのビルド

ではまずDevelopmentモードでアプリをテストしていきます。

まずは作業ルートフォルダに戻って作業します。

            
            $ yarn dev
yarn run v1.22.5

  Your application is ready~! 🚀

  - Local:      http://0.0.0.0:4200
  - Network:    http://172.24.0.2:4200
        
これでローカルでWebサーバーが立ち上がり、指定したポート(ここでは4200)で開くと以下のように最初のSveleteアプリが作成されました。

合同会社タコスキングダム|蛸壺の技術ブログ

続いて、このアプリをProductionとしてビルドしてみましょう。

            
            $ yarn build
yarn run v1.22.5
  rollup -c

src/main.js → public/build/bundle.js...
created public/build/bundle.js in 939ms
Done in 1.28s.
        
バンドルされたリリース版のSvelteアプリは、public/buildフォルダ以下にまとめられています。

            
            $ ls public/build/
bundle.css  bundle.js

$ du -ab public/build/
225   public/build/bundle.css
3362  public/build/bundle.js
        
ちなみにSvelteの最大の魅力となっている他のフレームワークと比較して最終的なバンドルサイズが非常にコンパクトになることが挙げられます。

見ての通りで、バンドルされたファイルもデフォルトでminifyされており、非常にスッキリと軽量なJSアプリで出力されています。

SvelteでSassを使う

先程の手順までで、基本的なSvelteアプリを開発する環境が整いました。

しかし、デフォルトではSassが使えないでのピュアCSSだけで開発を進めていくのはちょいと難しいかも知れません。

追加でSassが使えるようにします。

まず外部からSassファイル・
●●.scssをインポートしないでsvelteファイルの<style>タグにインラインで使うために、以下のパッケージを追加します。

            
            $ yarn add sass svelte-preprocess -D
        
次にrollup.config.jsを以下のように追加編集しておきます。

            
            ///...中略

//👇プリプロセスプラグインを追加
import sveltePreprocess from 'svelte-preprocess';

///...中略

function serve() {
    ///...中略
    export default {
        ///...中略
        plugins: [
            svelte({
                compilerOptions: {
                    dev: !production
                },
                //👇プリプロセスプラグインを注入
                preprocess: sveltePreprocess()
            }),
            //...以下略
    };
        
これで準備は完成で、あとは何かsvelteファイル内のインラインスタイルで、

            
            ...
<style lang="scss">
    //...Sassのスタイルコード
</style>
        
とすることで利用可能になります。

@importを使うことで、外部定義のSassファイルもSvelteで使うことも可能です。

            
            ...
<style lang="scss">
@import "./hoge.scss";
@import "./styles/piyo.scss";
</style>
        
なお、@importはSvelteファイルからの相対パスで記述されるので、パスの記述が正しいかのチェックは必要です。

VSCode内でSvelteのLinter対策

Svelteコードのハイライトエクステンションをインストールし、lang="scss"のタグを使うと、以下のような警告が執拗に張り付いてきて困るときがあります。

合同会社タコスキングダム|蛸壺の技術ブログ

これはSvelete側のエラーではなく、VSCodeがデフォルトで
node-sassを探しにいって、Sassモジュールどれか混乱していることが原因のようです。

なお
node-sassは現在非推奨になっています(詳しくは以下の記事で説明しています)。

まずは追加でプラグインに必要なパッケージを入れておきます。

            
            $ yarn add autoprefixer postcss -D
        
そしてプロジェクトのルートにsvelte.config.jsを追加して以下のようにDart-Sassが見れるように編集しておきます。

            
            const sveltePreprocess = require('svelte-preprocess');
const production = !process.env.ROLLUP_WATCH;

const preprocessOptions = {
    sourcemap: !production,
    defaults: {
        script: "typescript",
        style: "scss",
    },
    postcss: {
        plugins: [require('autoprefixer')()]
    }
};

module.exports = {
    preprocess: sveltePreprocess(preprocessOptions),
    preprocessOptions,
}
        

Typescriptのインストール

プロジェクトのルートで、

            
            $ mkdir scripts && cd scripts
$ wget https://raw.githubusercontent.com/sveltejs/template/master/scripts/setupTypeScript.js
$ cd ../
$ node scripts/setupTypeScript.js
Converted to TypeScript.

You will need to re-run your dependency manager to get started.
        
とすることで、typescriptがスクリプトで利用できるようになります。

npmパッケージもいくつか追加されているので、

            
            $ yarn install
        
も忘れずにやっておきましょう。

ちなみにこのスクリプトはプロジェクトの
rollup.config.jsに結構強引に追加してくれるため、先行して手動設定していたモジュールも平気で重複インポートさせてくれます。

            
            [!] Error: Identifier 'sveltePreprocess' has already been declared
rollup.config.js (10:7)
 8: import css from 'rollup-plugin-css-only';
 9: 
10: import sveltePreprocess from 'svelte-preprocess';
           ^
        
二回も読み込んだらビルドエラーが出現します。

そこで
rollup.config.jsで重複インポートしている部分を手動で削除するような対策が必要です。


まとめ

以上、今回ははじめてのSvelteアプリをAlpine Dockerコンテナ上で立ち上げるための概論のようなものを説明していきました。

Svelteは後発のJSフレームワークであったものの、最近では徐々に存在感が増し、有名なプロジェクトにも採用されるケースが多くなってきている注目のプログラムです。

この機会に、何か自前でSvelteアプリでも一つ作って遊んでみてはいかがでしょうか。

参考サイト

Svelte Tutorial|日本語訳(Svelteがざっとダイジェストで学べます)

Svelte Documentation|日本語訳(少し変な邦訳が多いです...)

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。