1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

gulpコマンドだけでDjangoをrunserverしてscssも監視してコンパイルしてホットリロードもしたい。

Posted at

Djangoでモダンなコーディング環境を作りたい

最近、

Djangoを書く案件があり、pythonも楽しいしview, model等の操作性がいいので毎日楽しかったのですが、フロント開発の際にいつも通りpugやらscssやらモダンな言語使ってガンガン書こうとしたら壁に当たりました。てかdjango-htmlってpug使えるのか?そもそもgulpどうやって導入するんだ?

え?素のhtmlとcssでコーディングするの...?

その日は絶望して寝ました。

翌日、

gulpをなんとかDjangoに導入してscssをコンパイルできるようになりました。
しかし、静的サイトでは問題なく動くgulpのbrowser-syncが、Djangoでは動きませんでした。
Djangoのプロジェクト起動コマンドpython3 manage.py runserverを打ち、gulpを監視状態にしてscssを書き、保存したらブラウザを手動リロード。

これのどこがモダンやねん

その日は絶望して寝ました。

翌日、

いろいろあり悟りを開きました。その結果、ターミナルで

gulp

と打つと

python3 manage.py runserverが実行されてDjangoプロジェクトが起動する。
さらに、gulpが動いてbrowser-syncでlocalhostにプロジェクトを表示。
同時に、scssを監視し始める。scssに変更があれば直ちにコンパイルして localhost をリロード。

するようになりました。
ちなみにpug導入は成功しませんでした。うまくいったら続編を書きます。

正直なところ、かなり荒削りに書いているので詳細な仕様などわからないことが多いですが、私と同じ思いをしている人間が数多いることを信じて、完成したgulpfile.jsをここに遺します。

この記事を理解するための前提条件目安

Django触ったことある
npm触ったことある
gulp触ったことある

くらいです。

環境

ハードウェア情報
MacBookPro15,4
Quad-Core Intel Core i7
django環境情報
Python 3.8.9
pip 22.1.2 
Django 2.2.27

ディレクトリ構成

+がフォルダを表しています。

通常のディレクトリ構成
+ mysite/
  + app/
    + static/
      + app/
        + css/
          + style.css
    + template/
      + app/
        + index.html
  + mysite/
    + settings.pyなど
  manage.py

上記は、Djangoのプロジェクト名をmysite、アプリ名をappとした場合の構成です。

Djangoにてstatictemplateの設定が済んでいる前提です。
この記事を読んでいる人はほとんど済んでいるはずです...よね?
ちんぷんかんぷんという人はこの記事を読めば理解できるかも

npm導入

プロジェクトルートディレクトリ(mysite/)にてnpmを導入します
また、必要モジュールをインストールします。

zsh
npm init y
npm i -D browser-sync gulp gulp-plumber gulp-sass sass

以下のようにpackage.jsonに追記されたと思います。

package.json
  "devDependencies": {
    "browser-sync": "^2.27.10",
    "gulp": "^4.0.2",
    "gulp-plumber": "^1.2.1",
    "gulp-sass": "^5.1.0",
    "sass": "^1.53.0",
  },

次に、
mysite/app/src/app/css/style.scssとなるようにフォルダを作成して空のscssファイルを用意します。
また、プロジェクトルートディレクトリにgulpfile.jsを追加します。空で大丈夫です。

gulp導入後のディレクトリ構成
+ mysite/
  + app/
    + src/
      + app/
        + scss/
          + style.scss // 追加した
    + static/
      + app/
        + css/
          + style.css
    + template/
      + app/
        + index.html
  + mysite/
    + settings.pyなど
  + node_modules/
  .gulpfile.js // 追加した
  manage.py
  package-locl.json // 増えた
  package.json // 増えた

gulpfile.js書いていく

先ほどプロジェクトルートディレクトリに追加したgulpfile.jsを書いていきます。
gulp4に対応したモダンな書き方になっているので見慣れたgulp記法と違う箇所があるかもしれません。

まず、必要モジュールを読み込みます。

gulpfile.js
const { src, dest, watch, parallel, series } = require('gulp')
const sass = require('gulp-sass')(require('sass'))
const plumber = require('gulp-plumber')
const browserSync = require('browser-sync').create()
const child = require('child_process')

続いて、django起動とホットリロードを有効にする関数を定義します。

この部分についてはほとんどこの記事を参考にしました。マジ感謝。

軽く解説すると、先ほど読み込んだchild_processから、spawnを使用してdjangoのrunserverを実現しています。spawnについてはこの記事が参考になります。少し古いですが。

また、browserSyncにてproxyportオプションを使ってDjangoと紐づけています。ここに関しては正直よくわかっていません。たくさん試したらこれで成功したという程度です。むしろ正しい方法教えてください。

ついでに、reload関数も定義します。こちらはbrowserSyncをリロードするだけの機能を持ちます。
のちほどwatch関数で呼び出すことで力を発揮します。

gulpfile.js
// runserver
var server = null
const runserver = (cb) => {
  if (server) {
    server.kill()
    console.log("kill task done!")
  }
  server = child.spawn('python3', ['manage.py', 'runserver'], { 
    stdio: 'inherit'
  })
  server.on('close', (code) => {
    if (code !== 0) {
      cb(code)
      console.error('Django runserver exited with error code: ' + code)
    } else {
      cb()
      console.log('Django runserver exited normally.')
    }
  })
}
// browser
const browser = (cb) => {
  browserSync.init({
    ui: false,
    notify: false,
    proxy: {
      target: "http://127.0.0.1:8000",
    },
    port: 8000
  })
}
// reload
const reload = (cb) => {
  browserSync.reload()
  cb()
}

python manage.py runserverでdjango起動する人はserver = child.spawn('python' ~ になります。
筆者はpython3 manage.py runserverでdjango起動するのでserver = child.spawn('python3' ~ です。

browser-syncのオプションであるtargetportも環境によって変わってくるかと思います。

さらに、scssをコンパイルする関数を定義します。

plumperはエラーが出た時に後述のwatch関数を止めないためにあります。
また、browserSync.stream()を最後に渡すことでファイル保存時、browser-syncに変更差分のみ綺麗に伝えてくれます。

gulpfile.js
const compileScss = () => {
  return src('./app/src/app/scss/**/*.scss')
    .pipe(plumber())
    .pipe(sass())
    .pipe(dest('./app/static/app/css/'))
    .pipe(browserSync.stream())
}

watch関数を定義します。

watch関数にてscssを監視し、先ほど定義したコンパイル関数とリロード関数を渡します。
こうすると、監視対象に変更があった際に指定した関数を実行してくれます。

gulpfile.js
const watchSass = () => {
  watch('./app/src/app/scss/**/*.scss', series(compileScss, reload))
}

最後に、関数を外部から呼び出せるようにして一つにまとめます。

gulp3から4への変更ではここが一番大きな変更かもしれませんね。

exports.defaultとすることでgulpのデフォルトコマンドに任意の関数を指定できます。
また、parallelは関数を並列に処理するためのものです。

gulpfile.js
exports.runserver = runserver
exports.browser = browser
exports.watchSass = watchSass

exports.default = parallel(
  runserver,
  browser,
  watchSass,
)

実行

長かったですね。すいません。
ターミナルにてプロジェクトルートディレクトリに移動し、gulpを実行します。

zsh
gulp

しばらく待つと、localhostにてプロジェクトが表示されるはずです。
その状態で、mysite/app/src/app/css/style.scssを編集して保存すれば、コンパイルされてmysite/statoc/app/css/に書き出されると思います。また、画面もリロードされるはずです。

完全版 gulpfile.js

これまで解説したgulpfile.jsをまとめたものを掲載します。

gulpfile.js
gulpfile.js
const { src, dest, watch, parallel, series } = require('gulp')
const sass = require('gulp-sass')(require('sass'))
const plumber = require('gulp-plumber')
const browserSync = require('browser-sync').create()
const child = require('child_process')

/* -------------------------------------------
  RUNSERVER, BROWSER
------------------------------------------- */

// runserver
var server = null
const runserver = (cb) => {
  if (server) {
    server.kill()
    console.log("kill task done!")
  }
  server = child.spawn('python3', ['manage.py', 'runserver'], { 
    stdio: 'inherit'
  })
  server.on('close', (code) => {
    if (code !== 0) {
      cb(code)
      console.error('Django runserver exited with error code: ' + code)
    } else {
      cb()
      console.log('Django runserver exited normally.')
    }
  })
}
// browser
const browser = (cb) => {
  browserSync.init({
    ui: false,
    notify: false,
    proxy: {
      target: "http://127.0.0.1:8000",
    },
    port: 8000
  })
}
// reload
const reload = (cb) => {
  browserSync.reload()
  cb()
}

/* -------------------------------------------
  SCSS
------------------------------------------- */

const compileScss = () => {
  return src('./app/src/app/scss/**/*.scss')
    .pipe(plumber())
    .pipe(sass({outputStyle: 'expanded'}))
    .pipe(dest('./app/static/app/css/'))
    .pipe(browserSync.stream())
}

/* -------------------------------------------
  WATCH
------------------------------------------- */

const watchSass = () => {
  watch(paths.scss + '**/*.scss', series(compileScss, reload))
}

/* -------------------------------------------
  EXPORTS
------------------------------------------- */

exports.runserver = runserver
exports.browser = browser
exports.watchSass = watchSass

exports.default = parallel(
  runserver,
  browser,
  watchSass,
)

所感

自分の環境だと、実行後にしばらくエラーが出てから綺麗に表示されるまでのラグがあります。
そんな不安定なコードを紹介すんなよという感じですが、これを構築するだけで2日かかってますので、自分と同じ思いをしている人のためにもとりあえず書きました。もっといい方法あるよという方がいたら是非教えていただきたいです。

また、今回はscssをコンパイルするコードのみですが、django-htmlもモダンに記述できる方法がないか模索中です。ちなみに、pugを導入しようとして一度挫折しています。こちらについてもいい情報ありましたら是非教えてください。

以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?