目標
この記事のゴールは、Webpackで以下のことができる設定を紹介することです。
- SPAではなく複数のHTMLをPugを利用して生成する
- 複数のJSをそれぞれBabelでトランスパイルする(1つにはまとめない)
- 複数のSassもそれぞれコンパイルする(1つにはまとめない)
- CSSにAutoprefixerなどのpostcssをかける
- CSSを部分的にインラインにもしたい
- トランスパイルしないファイル(
.png
みたいなもの)も扱いたい
先に結果から
とりあえず webpack.config.js
を貼っておきます。
// yarn add apply-loader autoprefixer babel-core babel-loader babel-preset-env copy-webpack-plugin css-loader extract-text-webpack-plugin globule node-sass postcss postcss-loader pug pug-loader sass-loader style-loader webpack webpack-dev-server
// develop : webpack-dev-server --open
// build : NODE_ENV=production webpack
const webpack = require('webpack')
const path = require('path')
const globule = require('globule')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// ディレクトリの設定
const opts = {
srcDir: path.join(__dirname, 'src'),
destDir: path.join(__dirname, 'public')
}
// keyの拡張子のファイルが、valueの拡張子のファイルに変換される
const convertExtensions = {
pug: 'html',
sass: 'css',
js: 'js'
}
// トランスパイルするファイルを列挙する
// _から始まるファイルは、他からimportされるためのファイルとして扱い、個別のファイルには出力しない
const files = {}
Object.keys(convertExtensions).forEach(from => {
const to = convertExtensions[from]
globule.find([`**/*.${from}`, `!**/_*.${from}`], {cwd: opts.srcDir}).forEach(filename => {
files[filename.replace(new RegExp(`.${from}$`, 'i'), `.${to}`)] = path.join(opts.srcDir, filename)
})
})
// pugでトランスパイルする
const pugLoader = [
'apply-loader',
'pug-loader'
]
// Sassをトランスパイルし、autoprefixerをかけるようにする
const sassLoader = [
{
loader: 'css-loader',
options: {
minimize: true
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()]
}
},
'sass-loader'
]
// Babelでトランスパイルする
const jsLoader = {
loader: 'babel-loader',
query: {
presets: ['env']
}
}
const config = {
context: opts.srcDir,
entry: files,
output: {
filename: '[name]',
path: opts.destDir
},
module: {
rules: [
{
test: /\.pug$/,
use: ExtractTextPlugin.extract(pugLoader)
},
{
test: /\.sass$/,
oneOf: [
{
// pugから `require('./hoge.sass?inline')` のように呼ばれた時は、ExtractTextPluginをかけない
resourceQuery: /inline/,
use: sassLoader
},
{
// それ以外の時は、単純にファイルを生成する
use: ExtractTextPlugin.extract(sassLoader)
}
]
},
{
test: /\.js$/,
exclude: /node_modules(?!\/webpack-dev-server)/,
use: jsLoader
}
]
},
plugins: [
new ExtractTextPlugin('[name]'),
// convertExtensionsに含まれていないファイルは、単純にコピーする
new CopyWebpackPlugin(
[{from: {glob: '**/*', dot: true}}],
{ignore: Object.keys(convertExtensions).map((ext) => `*.${ext}`)}
),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
devServer: {
contentBase: opts.destDir,
watchContentBase: true
}
}
if (process.env.NODE_ENV === 'production') {
config.plugins = config.plugins.concat([
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin()
])
}
module.exports = config
以下は、この設定ファイルの説明だけなので、困った時にでも読んでください。
動機
僕はRailsが大好きですが、動的に生成する必要が無いサイトを作ることもたくさんあります。
そんな時に、今の時代HTML/CSS/JSを生で書くわけにもいかないので、トランスパイルしたくなるけど、どの静的サイトジェネレータもいまいちしっくりこない。
できれば、CSSはSassを使ってトランスパイルしたいし、autoprefixしてほしい。JSもバベりたい。
ということでWebpackをコテコテに設定して、いい感じにトランスパイルしてくれるようにしました。
仕組み
「トランスパイルするもの」と「それ以外」
トランスパイルするものは、Webpackの設定で、
それ以外のものは、そのままCopyWebpackPluginというもので、opts.destDir
にコピーします。
複数ファイル出力
Webpackはentry
に複数のキーを持ったObjectを指定すると、
{
"出力先ファイル名": "ソースファイル"
}
の形で複数ファイルを出力してくれます。
これを利用して、JSもHTMLもCSSも、一緒くたに生成してしまいましょう。
HTML
まずHTMLですが、僕はPugが好きなので、Pugを使います。
pug-loader
はpugテンプレートから、requireすると「HTMLを生成するようなfunction」を返してくれるJSを生成するLoaderなので、これだけだと困ります。
なのでapply-loader
でfunctionを一度呼んでHTMLを生成することにします。
テンプレートのファイルは_hoge.pug
のようにアンダーバーから始まるファイル名にすれば、トランスパイルのリストから除外することができます。
アクセスするURLをきれいにしたりしたい時は、files
のキーをいい感じに、いじると良さそうです。
SASS
<link>
タグで読み込むCSSはそのまま、Sassで書きましょう。
link(rel="stylesheet" href=`/hoge.css?${+ new Date()}`)
みたいに書くと、キャッシュを無効化できて便利です。
(本当はWebpackのHashを使いたかったのですが、Pugから取得することが難しかったので今回はやめました)
<style>
タグで、インラインに展開したいCSSは、
style= require("./hoge.sass?inline")
の様に書けば、インラインで展開できます。
インライン展開したい時は、ExtractTextPluginをかけないようにしないと、変な展開のされ方をしてしまいます。
@import
で読み込むSassは、名前をアンダーバーから始めれば、それ単体ではトランスパイルされなくなります。
JS
JSは普通にBabelでトランスパイルするだけです。
Webpackの本来のお仕事なので、特に難しいことはありません。
その他
webpack-dev-server
ですがJSを読み込んでいないHTMLでは、自動リロードが効かないようです。
これを直す方法が未だに見つけられていないので、もし知っている方がいたら教えてください。
それから今の所、ファイルが新規追加されたときには、手動でwebpack-dev-server
を起動し直さなければなりません。何かしら便利なコマンドなどを知っている方がいらっしゃいましたら、こちらも教えていただけると助かります。