AI can fly !!

AI がやりたい Web エンジニアのアウトプット (AI の知識は無い)

【Django3.0】TypeScript と Sass でフロントエンドを書く【webpack4】

django-logo

はじめに

Django で Web アプリケーションを開発する際、フロントエンドを TypeScript と Sass で書いて webpack でトランスパイル & バンドルする方法をまとめました。

ここでのフロントエンドとは、 Angular や React といったフレームワークを使わずに、 TypeScript と Sass で書いたファイルを webpack で JavaScriptCSS にトランスパイルして、静的ファイルとして Django テンプレートで読み込むことを指しています。

前提条件

  • Django がインストールされており、プロジェクトとアプリケーションの作成・設定が完了している
  • Node.js がインストールされており、npm init を行い package.json が作成されている

Django のインストールと初期設定については、以下の記事を参考にしてください。

ai-can-fly.hateblo.jp

動作環境

OS Version
Windows 10 Pro 1909
Application Version
PowerShell 5.1.18362.752
Environment Version
Node.js 12.16.2
npm 6.14.4
Language Version
Python 3.8.1
Package Version
Django 3.0.4
webpack 4.43.3
webpack-cli 3.3.11
typescript 3.8.3
ts-loader 7.0.1
sass 1.26.5
sass-loader 8.0.2
css-loader 3.5.3
mini-css-extract-plugin 0.9.0
postcss-loader 3.0.0
autoprefixer 9.7.6

最終的にやりたいこと

  • Django のアプリケーション毎に static ディレクトリを作り、その中に TypeScript と Sass のファイルを置く
  • webpack の entry (エントリーポイント) と output (出力先ディレクトリ) を、 Django のアプリケーション単位に設定にする
  • webpack で TypeScript と Sass を、それぞれ JavaScriptCSS にトランスパイルする
  • Django のテンプレートで、トランスパイルされた JavaScriptCSS を読み込む

Django プロジェクトの構成

[django_project]
├ [django_application_a]
│ ├ static
│ │ └ [django_application_a]
│ │   ├ css
│ │   │ ├ index.scss
│ │   │ └ main.bundle.css      ← [application_a] のバンドルファイル
│ │   └ js
│ │     ├ index.ts             ← [application_a] のエントリーポイント
│ │     └ main.bundle.js       ← [application_a] のバンドルファイル
│ └ templates
│   └ [django_application_a]
│     └ index.html             ← main.bundle.js と main.bundle.css を読み込むテンプレートファイル
└ [django_application_b]
  ├ static
  │ └ [django_application_b]
  │   ├ css
  │   │ ├ index.scss
  │   │ └ main.bundle.css      ← [application_b] のバンドルファイル
  │   └ js
  │     ├ index.ts             ← [application_b] のエントリーポイント
  │     └ main.bundle.js       ← [application_b] のバンドルファイル
  └ templates
    └ [django_application_b]
      └ index.html             ← main.bundle.js と main.bundle.css を読み込むテンプレートファイル

エントリーポイント

index.ts

import '../css/index.scss'

console.log('Hello TypeScript and Sass !!');

Django アプリケーションのエントリーポイントの TypeScript ファイルで、 Sass ファイルをインポートします。

パッケージインストール

必要な npm パッケージをインストールします。

npm ではなく Yarn でも可です。

webpack

npm install --save-dev webpack webpack-cli

webpackCLI で webpack を操作するために webpack-cli をインストールします。

TypeScript

npm install --save-dev typescript ts-loader

typescript と webpack で TypeScript を読み込むために ts-loader をインストールします。

Sass

npm install --save-dev sass sass-loader

sass と webpack で Sass を読み込むために sass-loader をインストールします。

CSS

npm install --save-dev css-loader mini-css-extract-plugin postcss-loader autoprefixer

@import などの依存関係の解決に css-loaderCSS ファイルを出力するために mini-css-extract-plugin 、ベンダープレフィックスを付与するために postcss-loaderautoprefixer をインストールします。

ちなみに、ベンダープレフィックスの付与は必須ではありません。

設定ファイル

TypeScript

tsc コマンドで tsconfig.json を作成し、トランスパイルの設定を記述します。

npx tsc --init

tsconfig.json

{
  "compilerOptions": {
    "module": "es2015",  // モジュール出力
    "sourceMap": true,  // ソースマップ出力
    "target": "es5",  // 変換先 ECMAScript バージョン
    "strict": true,  // 以下のオプションを一括で有効化
                     // --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictBindCallApply, 
                     // --strictNullChecks, --strictFunctionTypes, --strictPropertyInitialization
    "forceConsistentCasingInFileNames": true  // ファイル名の大文字小文字を区別する
  }
}

webpack

webpack の設定ファイルは CLI コマンドからの生成は出来ないので、ファイルを手動で作成します。

webpack.config.js

const path = require('path');
const autoprefixer = require('autoprefixer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // モード
  mode: 'development',

  // エントリーポイント
  entry: {
    '[application_a]/static/[application_a]/js/main': path.resolve(
      __dirname,
      '[application_a]/static/[application_a]/js/index.ts'
    ),
    '[application_b]/static/[application_b]/js/main': path.resolve(
      __dirname,
      '[application_b]/static/[application_b]/js/index.ts'
    )
  },

  // ファイル出力先
  output: {
    // 出力先ディレクトリ
    path: __dirname,
    // 出力ファイル名
    filename: '[name].bundle.js',
  },

  // ソースマップ
  devtool: 'cheap-module-eval-source-map',

  // ローダー
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              esModule: true,
            },
          },
          {
            loader: 'css-loader',
            options: {
              url: false,
              sourceMap: true,
              importLoaders: 2,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [autoprefixer()],
            },
          },
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                includePaths: ['./node_modules'],
              },
              sourceMap: true,
            },
          },
        ],
      },
    ],
  },

  // モジュール解決
  resolve: {
    extensions: ['.ts', '.js'],
  },

  // プラグイン
  plugins: [
    new MiniCssExtractPlugin({
      moduleFilename: ({ name }) =>
        `${name.replace('/js/', '/css/')}.bundle.css`,
    }),
  ],
};

Django アプリケーション毎に JavaScript ファイルと CSS ファイルを出力するために、以下の点を考慮します。

  • entry には Django アプリケーション単位でエントリーポイントを指定する
  • entry に指定するオブジェクトのキーに出力先パスとファイル名の一部を指定する
  • outputJavaScript の出力ファイル名を指定する
  • pluginsMiniCssExtractPluginCSS の出力ファイル名を指定する

ポイントは entry で指定したオブジェクトのキーが、 output[name]pluginsMiniCssExtractPluginname に代入されるということです。

entry のキーにはファイル名だけでなく、パスを含む文字列が指定できるので、 Django アプリケーション毎のエントリーポイントまでのパスをキーとすることで、結果的にバンドルファイルも Django アプリケーション毎に出力することができます。

package.json

webpack を使用する上で必ず必要なものではありませんが、 npm-script によく使うコマンドを登録しておくと便利です。

{
  "name": "django-project",
  "version": "0.0.1",
  "main": "index.js",
  "private": true,
  "devDependencies": {
    "autoprefixer": "^9.7.6",
    "css-loader": "^3.5.3",
    "mini-css-extract-plugin": "^0.9.0",
    "postcss-loader": "^3.0.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "ts-loader": "^7.0.1",
    "typescript": "^3.8.3",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11"
  },
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch",
    "prod": "webpack --mode=production"
  }
}

ここでは通常の webpack コマンドに加え、開発時によく使う watch オプションと、本番用の production モードの三種類を npm-script に追加しました。

テンプレートファイル

Django のテンプレートファイルには、 webpack でコンパイルされた JavaScript ファイルと CSS ファイルを読み込むように記述します。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="{% static "[application_name]/css/main.bundle.css" %}">
    <title>Django</title>
  </head>
  <body>
    Hello Django !!
    <script src="{% static "[application_name]/js/main.bundle.js" %}"></script>
  </body>
</html>

webpack の実行

package.jsonnpm-script として webpack のコマンドを登録している場合は、 npm run コマンドで webpack を実行します。

npm-script ではなく、直接 webpack コマンドで実行しても同様なので、この辺りはお好みで。

開発用

# npm-script
npm run watch

# npx
npx webpack --watch

watch オプションを付けて webpack コマンドを実行すれば、コードを変更する度に再コンパイルされるので、開発時に都度 webpack コマンドを実行する必要が無くなります。

Web 上で、「watch オプションを使用した場合、変更したコードの差分のみのコンパイルとなるので処理が高速化する」という記事をいくつか目にしましたが、 webpack 公式ドキュメントでは同様の記述を見付けられなかったので、真偽のほどは不明です…

本番用

# npm-script
npm run prod

# npx
npx webpack --mode=production

modeproduction に設定すると、 minimizetrue になるなど、本番環境での使用を想定したコンパイルが行われます。

出力ファイル

コンパイルされたファイルは、各 Django アプリケーション毎に static ディレクトリの js / css ディレクトリ内にそれぞれ出力されます。

[django_project]/[django_application]/static/[django_application]/js/main.bundle.js
[django_project]/[django_application]/static/[django_application]/css/main.bundle.css

テンプレートファイルには、出力された JavaScript ファイルと CSS ファイルを読み込むように記述します。 (テンプレートファイル を参照)

おわりに

今回紹介した内容は、単純に Django で TypeScript と Sass が使えるようになっただけではなく、 npm で配布されている様々なパッケージが使用できるようになったことを意味します。

例えば、マテリアルデザインを手軽に実装できる Material Components や 老舗の Bootstrap 、 JavaScript でグリッドを出力できる ag-Grid など、 npm でパッケージをインストールして使用すれば、 webpack がまとめてコンパイルしてくれます。

CDN から読み込んで使用する方法もありますが、ローカルにソースがあれば IDE の自動補完や定義への移動が使えるので、個人的には npm でインストールしてコンパイルする方をお勧めします。

条件を満たしていれば、 Tree Shaking によるデッドコード除去や、 Code Splitting での遅延ロードの実現など、一概に webpack より CDN の方が良いとは言い切れないと思いますので、まずはローカルインストールでもいいのではないでしょうか。

これで TypeScript + Sass + Django の組み合わせで Web アプリが作れるようになりました。

もうコードを書かない理由は無くなりましたね _(┐「ε:)_