FlaskでWebアプリを開発している。その際、WebブラウザにCSSがキャッシュされてしまう問題に、どう対処するか考えてみた。
HTML内でCSSをリンクする際、style.css?v=12
などのように手動で、更新日時やバージョンを付け加えている場合がある。しかし、せっかくPythonを使っているので、これを自動で解決してもらいたい。
模範解答
検索してみると模範解答は、以下のように、url_forを書き換えると良いという回答だ。
@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)
そして、テンプレートでは、以下のようにリンクする
<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.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)
テンプレートは以下のように書く。
<link rel="stylesheet" type="text/css"
href="{{ 'style.css' | staticfile }}">
模範解答より、ずっとエレガントにできた。
とは言え、この方法では、テンプレートエンジンによって、HTMLがキャッシュされてしまっているため、瞬時にキャッシュが更新されないのが問題だ。それでも、一応、style.css?v=xxxのようになる。
手抜き回答 その2 context_processorを追加する
やはり、模範解答を咀嚼して、context_processorを利用するようにするのはどうかと考えた。
@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)
そしてテンプレートは以下の感じ
<link rel="stylesheet" type="text/css"
href="{{ staticfile('style.css') }}">
これなら、キャッシュされず、瞬時に値が反映されるし、それほど見た目が悪くないから良いかな?
所感
Flask + jinja2の組み合わせ、気軽に使えて、拡張も楽々でお気に入り。
得た知見は、以下の通り。
- フィルタにすると、テンプレートを変更しないと内容が書き換わらない。
- コンテキストプロセッサーを使うと、テンプレートへの埋め込み処理の以前に実行されるので、常に値が最新にできる。