12
12

More than 3 years have passed since last update.

FlaskでWebアプリ開発中にCSSが反映されない問題を解決する

Last updated at Posted at 2020-02-08

FlaskでWebアプリを開発している。その際、WebブラウザにCSSがキャッシュされてしまう問題に、どう対処するか考えてみた。

HTML内でCSSをリンクする際、style.css?v=12などのように手動で、更新日時やバージョンを付け加えている場合がある。しかし、せっかくPythonを使っているので、これを自動で解決してもらいたい。

模範解答

検索してみると模範解答は、以下のように、url_forを書き換えると良いという回答だ。

app.py
@app.context_processor
def override_url_for():
    return dict(url_for=dated_url_for)

def dated_url_for(endpoint, **values):
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = os.path.join(app.root_path,
                                 endpoint, filename)
            values['q'] = int(os.stat(file_path).st_mtime)
    return url_for(endpoint, **values)

そして、テンプレートでは、以下のようにリンクする

hoge.html
<link rel= "stylesheet" type= "text/css" 
 href= "{{ url_for('static',filename='style.css') }}">

コンテキストプロセッサーの、url_forを上書きするテクニックだ。エンドポイントにstaticが指定された時に、ファイル更新日を付け加えるというテクニックだ。

ちなみに以下が元ネタの英語ページ。日本の他のブログでもこれを参考に紹介されていた。
(参考) https://stackoverflow.com/questions/21714653/flask-css-not-updating

この回答に不満というか、Flaskのurl_forに不満

ただし、いつも思うのだが、静的なリンクを書くだけなのに、url_forを使うのは長い。手間だ。Flaskでは、静的ファイルを配置するのは/static以下とデフォルトで決まっているので、CSSを配置するパスも既に決まっているはず。つまり、静的ファイルを埋め込むだけなのに、url_forを使うのは、かなり面倒に感じる。

そこで、手抜きして、以下のようにする方法を考えてみた。

手抜き回答 - 半分失敗の例

最初にフィルタを使う方法だ。

app.py
@app.template_filter('staticfile')
def staticfile_filter(fname):
    path = os.path.join(app.root_path, 'static', fname)
    mtime =  str(int(os.stat(path).st_mtime))
    return '/static/' + fname + '?v=' + str(mtime)

テンプレートは以下のように書く。

hoge.html
<link rel="stylesheet" type="text/css"
 href="{{ 'style.css' | staticfile }}">

模範解答より、ずっとエレガントにできた。

とは言え、この方法では、テンプレートエンジンによって、HTMLがキャッシュされてしまっているため、瞬時にキャッシュが更新されないのが問題だ。それでも、一応、style.css?v=xxxのようになる。

手抜き回答 その2 context_processorを追加する

やはり、模範解答を咀嚼して、context_processorを利用するようにするのはどうかと考えた。

app.py
@app.context_processor
def add_staticfile():
    def staticfile_cp(fname):
        path = os.path.join(app.root_path, 'static', fname)
        mtime =  str(int(os.stat(path).st_mtime))
        return '/static/' + fname + '?v=' + str(mtime)
    return dict(staticfile=staticfile_cp)

そしてテンプレートは以下の感じ

hoge.html
<link rel="stylesheet" type="text/css"
 href="{{ staticfile('style.css') }}">

これなら、キャッシュされず、瞬時に値が反映されるし、それほど見た目が悪くないから良いかな?

所感

Flask + jinja2の組み合わせ、気軽に使えて、拡張も楽々でお気に入り。
得た知見は、以下の通り。

  • フィルタにすると、テンプレートを変更しないと内容が書き換わらない。
  • コンテキストプロセッサーを使うと、テンプレートへの埋め込み処理の以前に実行されるので、常に値が最新にできる。
12
12
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
12
12