🤝

NimアプリをWebpackでバンドルする

2022/05/22に公開

はじめに

プログラミング言語 Nim は通常 C にコンパイルされ,ネイティブな実行可能ファイルを生成します.
実は,Nim は C 以外にも C++ や JavaScript などの他のターゲットにコンパイルできます.

この記事では,Nim を JavaScript の代替(いわゆる AltJS[1]?)として用いて,Web アプリケーションを Nim で作成し,Webpack でバンドルするまでの前準備についてご紹介します.
また,後半では Nim で SPA を制作する Karax を用いた例もご紹介します.

Nim を Webpack でバンドルする利点

Nim を Webpack を用いてバンドルする利点は,JavaScript 側のモジュール解決もバンドルできることにあるでしょう.
Nim にはそもそもモジュールシステムがあるため,Nim のみで開発する場合にはnim jsコマンドでバンドルまでしてくれます.
一方で,JavaScript と連携する場合には事情は異なります.
Nim のモジュールシステムと JavaScript のモジュールシステムとでは仕組みが異なるため,両者が混在するプロジェクトではバンドルが困難といえます.
Webpack を用いることで,こうした問題を解決できるのです.

環境

記事作成時点の各ツールのバージョンは以下の通りです:

  • 必須
    • yarn: 1.22.19
    • Nim: 1.6.6
    • Webpack: 5.72.1
    • Webpack CLI: 4.9.2
    • nim-loader: 0.3.3
  • 必須ではないが今回使ったもの
    • karax: 1.2.1
    • html-webpack-plugin: 5.5.0
    • CoffeeScript: 2.7.0

nim-loader に関する不具合報告は GitHub issues にお願いします.

初期設定からバンドルまで

まずは,プロジェクトを格納するディレクトリを作成します.

$ mkdir nim-webpack-sample
$ cd nim-webpack-sample

JavaScript プロジェクトの初期設定をします.
今回は Yarn を用いましたが,npm を使う場合は適宜読み替えましょう.

$ yarn init

Webpack, nim-loader をそれぞれインストールします.

$ yarn add --dev webpack webpack-cli nim-loader
$ yarn add --dev coffeescript # 必要な人のみ追加

Webpack の設定ファイルを作成します.
今回は JavaScript を一切書かないので,エントリファイルに Nim ファイルを指定してしまいましょう.

webpack.config.coffee
path = require('path')

nim_rule =
  test: /\.nim$/
  use: [{
    loader: 'nim-loader'
    options:
      flags: ['-d:debug'] # Nim compiler options here
  }]

module.exports =
  mode: 'development'

  entry: './src/main.nim' # Nim file as an entry point
  output:
    path: path.resolve(__dirname, 'dist')
    filename: 'main.bundle.js'

  module:
    rules: [nim_rule]

エントリポイントとなる Nim ファイルを,例えば以下のように作成します.

src/main.nim
echo "Hello, Webpack 🤝 Nim World!"

今回の設定例では,このファイルを ./src/ 配下に main.nim として保存しておきます.

ここまでで,環境構築は完了しました!
あとは,Webpack でバンドルを行うだけです.

$ yarn run webpack

これで,./dist/main.bundle.js としてバンドルされた JavaScript ファイルが生成されました🎉🎉🎉

JavaScript をエントリファイルとして Nim を呼び出す

JavaScript をエントリファイルとする場合は,ソースコードの書き方に工夫が必要です.

利用する Nim ソースコードを以下のように作成します.
FFI として module という名前の JS オブジェクトを作成し[3]module.exports に追加することで,Nim のプログラムを JavaScript 側でも利用できるようになります.

src/submodule.nim
import jsffi

var module {.importc,nodecl.}: JsObject

func add5(x: int): int = x + 5

module.exports.add5 = add5

JavaScript のソースコードは以下のように作成します.

src/main.js
import { add5 } from './submodule.nim'

console.log(add5(37)) // => 42

Webpack のエントリファイルを ./src/main.js に変更すれば,あとは同じようにバンドルを行うだけです.

Karax を使ってみる

Karax とは,Nim の SPA(Single Page Application) ライブラリです.
仮想 DOM を Nim のマクロを使って記述するという特徴があります.
Web アプリケーションのより具体的な実践例として,Karax を用いた例をご紹介します.

https://github.com/karaxnim/karax
https://qiita.com/momeemt/items/599c9959a66e7440690e

Karax をまだインストールしていない場合は,Karax をインストールしましょう.

$ nimble install karax

Webpack で HTML を扱うため,html-webpack-plugin をインストールします.

$ yarn add --dev html-webpack-plugin

テンプレートとなる HTML ファイルを作成します.

src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Nim + Webpack Sample App</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

Webpack 設定ファイルを編集して,HTML も同時に出力するようにします.

webpack.config.coffee
 path = require('path')
+HtmlWebpackPlugin = require('html-webpack-plugin')
 
 nim_rule =
   test: /\.nim$/
   use: [{
     loader: 'nim-loader'
     options:
       flags: ['-d:debug'] # Nim compiler options here
   }]
 
 module.exports =
   mode: 'development'
 
   entry: './src/main.nim'
   output:
     path: path.resolve(__dirname, 'dist')
     filename: 'main.bundle.js'
 
   module:
     rules: [nim_rule]
+
+  plugins: [
+    new HtmlWebpackPlugin({ template: './src/index.html' })
+  ]

Nim ファイルを編集して,Karax を用いた Web アプリケーションを制作してみましょう.

src/main.nim
import karax/[karax, karaxdsl, vdom]
from random import randomize, rand

proc createDom: VNode =
  result = buildHtml(tdiv):
    h1:
      text "Nim/Karax + Webpack Sample"

    p:
      if rand(100) < 50:
        text "Hello!"

      else:
        text "Aloha!"

randomize()
setRenderer createDom, "app"

最後にビルドをします.

$ yarn run webpack

生成された ./dist/index.html を開くことで,Karax アプリケーションの動作を確認できます.

まとめ

Nim で作られた Web アプリケーションを Webpack でバンドル化する方法についてご紹介しました.
皆様も Web アプリケーションを Nim で作ってみてはいかがでしょうか?

脚注
  1. 気になったのですが,AltJS という呼び方は何が発祥なのでしょうか.Alternatives to JavaScript の略であり,よく CoffeeScript や TypeScript などを説明するときに使われています.しかし,JavaScript にコンパイルできる言語という定義ならば,例えば C も Emscripten によって JavaScript にコンパイルできますし,必要なツール群が揃いさえすれば多くの言語が AltJS と言えてしまう気がします. ↩︎

  2. Nim が文法にオフサイドルールを採用しているので,プロジェクトの他のファイルもオフサイドルールを採用した言語で書かれていると,統一感が感じられて個人的に好きです.HTML の代わりに Pug を,CSS の代わりに Sass を使うとさらに統一感を演出できます. ↩︎

  3. importc プラグマを書くことで,コンパイル後も module という変数名で参照するようになります(これがないと module_123456789 のようにマングリングされてしまいます).nodecl プラグマを書くことで,コンパイル後のコードで変数宣言をしなくなります(importc さえあれば変数宣言はしなくなりますが,互換性のために書いておくことをおすすめします). ↩︎

Discussion