今天說好要提到 Webpack 的部分,然後,我最近其實有點疲憊,所以今天就先講一下目前在使用的 Webpack 就好了。

只是說,我可能不會多加解釋就是。


說好的 Webpack

'use strict'
/**
 * @author      Scar Wu, Hina Chen.
 */

// Load Libraries
const path = require('path')
const webpack = require('webpack')
const webpackLodashPlugin = require('lodash-webpack-plugin')
const webpackOptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const webpackMiniCssExtractPlugin = require('mini-css-extract-plugin')
const vueLoaderPlugin = require('vue-loader/lib/plugin')

function cssLoaders (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS
      ? [cssLoader, postcssLoader]
      : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }
    return [webpackMiniCssExtractPlugin.loader].concat(loaders)
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
function styleLoaders (options) {
  const output = []
  const loaders = cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]

    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

let extEntries = {}

if (process.env.NODE_ENV === 'production') {
  extEntries = {
  }
}

// Merge Webpack Config
const webpackConfig = {
  mode: process.env.NODE_ENV,
  context: path.resolve(__dirname, './'),
  entry: Object.assign({
    app: './src/vue/main.js'
  }, extEntries),
  output: {
    filename: 'js/[name].js',
    chunkFilename: 'js/[name].js',
    publicPath: '/vue/'
  },
  devtool: (process.env.NODE_ENV !== 'production') ? 'eval-source-map' : false,
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': path.resolve('./src/vue'),
      '@assets': path.resolve('./src/vue/assets')
    }
  },
  optimization: (process.env.NODE_ENV === 'production') ? {
    splitChunks: {
      chunks: 'all',
      name: 'vendor'
    },
    runtimeChunk: {
      name: 'manifest'
    }
  } : {},
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: cssLoaders({
            sourceMap: (process.env.NODE_ENV !== 'production'),
            extract: (process.env.NODE_ENV === 'production')
          }),
          cssSourceMap: (process.env.NODE_ENV !== 'production'),
          cacheBusting: true,
          transformToRequire: {
            video: ['src', 'poster'],
            source: 'src',
            img: 'src',
            image: 'xlink:href'
          }
        }
      },
      ...(styleLoaders({
        sourceMap: (process.env.NODE_ENV !== 'production'),
        extract: (process.env.NODE_ENV === 'production'),
        usePostCSS: true
      })),
      {
        test: /\.js$/,
        loader: 'babel-loader?cacheDirectory=true'
      },
      {
        test: /\.pug$/,
        loader: 'pug-plain-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'img/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'media/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'fonts/[name].[hash:7].[ext]'
        }
      }
    ]
  },
  plugins: [
    new vueLoaderPlugin(),
    ...((process.env.NODE_ENV !== 'production') ? [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NamedModulesPlugin(),
      new webpack.NoEmitOnErrorsPlugin()
    ] : []),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpackMiniCssExtractPlugin({
      filename: 'css/[name].css'
    }),
    new webpackOptimizeCSSPlugin({
      cssProcessorOptions: (process.env.NODE_ENV !== 'production')
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    new webpackLodashPlugin({
      caching: true,
      collections: true,
      paths: true,
      shorthands: true
    })
  ],
  node: {
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}

module.exports = webpackConfig

裡面的 extEntries 大抵上就是動態載入的部分,所以你會發現他只有在 Production 的環境底下才會被加入。因為在開發模式下,這種動態載入是會失效的,你還是需要在 main.js 裡面暫時的把他 require 進來方便開發。

然後在正式環境下,也會有 splitChunks, runtimeChunk 這兩組設定,所以,他會把從 entry 進來的檔案做好分割,這裡就是我們的 Vue 可以通用的原因。就是不會出現我 Vue 不是你的 Vue 的那種狀況。

至於其他的,就看看就好(欸)。


然後,如果你想用昨天的 CSS Modules 的話,可以更動 use: laoder 的部分,簡單的寫法大概像是這樣:

// 前略 ...
output.push({
  test: new RegExp('\\.' + extension + '$'),
  oneOf: [
    {
      resourceQuery: /module/,
      use: [
        'vue-style-loader',
        {
          loader: 'css-loader',
          options: {
            modules: true,
            localIdentName: '[local]_[hash:base64:5]'
          }
        },
        'sass-loader'
      ]
    },
    {
      use: loader
    }
  ]
})
// 後略 ...

至於那個 localIdentName 請不要用來惡搞你的客戶,感恩。不然大家好像都在我這邊學到一些不三不四的設定,這樣感覺超糟糕的。

我是糟糕的人沒關係,你們不可以!


小結

我最近比較沒梗一點,明天聊到大型資料載入的部分,在跟大家分享一些算是八奇實例。

ITHome 鐵人賽同步刊登 動態載入 webpack 簡易設定 Day 24