使用webpack4打造自己的前端工作流

wmui發表於2018-08-24

前言

webpack4釋出已經有一段時間了,我在實踐的過程中發現,很多專案配置在webpack3下工作正常,但是升級到webpack4直接就崩了,如果想要webpack4正常工作,很多外掛也需要升級到新版。下面是我使用webpack4配置的一個學習案例,包含了日常開發的常用配置項,比如多入口檔案配置、模板檔案自定義、版本號控制、js和css分離、css自動新增字首、scss轉css、圖片及字型檔案的處理、babel編譯JS語法和API等等

版本號

當我們修改程式碼後,需要重新打包檔案,這時候為了避免瀏覽器快取,往往需要為檔案新增一個版本號

var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject:'body', // 插入位置
      favicon: './favicon.ico', // icon圖示
      title: 'webpack learn', // 生成的html檔案的標題
      filename: 'index.html', // 生成的html檔名稱
      minify:{
        removeComments: false, // 刪除註釋
        collapseWhitespace: false // 刪除空格
      }
    })
  ]
}
複製程式碼

打包後的程式碼

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack learn</title>
    <link rel="shortcut icon" href="favicon.ico">
  </head>
  <body>
  <script type="text/javascript" src="main.js?f8f60ca3f6ee44382620"></script></body>
</html>
複製程式碼

html-webpack-plugin這個外掛會生成一個名為index.html、標題是webpack learn的檔案,並且自動把打包後的檔案插入到html中。如果不配置這個外掛,filename的預設值就是index.html,title預設是Webpack APP

inject表示插入的位置,預設是body,可選值head favicon表示可以新增一個icon圖示 minify表示對壓縮檔案,removeComments和collapseWhitespace的預設值都是false

模板檔案

html-webpack-plugin外掛可以使用模板檔案,這樣我們就可以自定義一個html檔案,然後讓這個外掛把打包後的檔案自動插入到模板檔案中

tmp/tmp.html模板檔案

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>template file</title>
  <body>
  <div>hello template</div>
</html>
複製程式碼

外掛配置項

plugins: [
  new HtmlWebpackPlugin({
    filename: 'index.html',
    template: './tmp/tmp.html'
  })
]
複製程式碼

打包後生成的index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>template file</title>
  <body>
  <div>hello template</div>
</html>
<script type="text/javascript" src="main.js?b321307e65d6b0fbee0b"></script>
複製程式碼

多頁面

對於多頁面一般會對應多個入口檔案,不同的html頁面輸出對應不同的入口檔案,html-webpack-plugin外掛支援配置多頁面。多頁面需要配置chunks和excludeChunks,chunks表示所包含的入口檔案,excludeChunks表示要排除的入口檔案

var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    a: './src/main-a.js',
    b: './src/main-b.js',
    c: './src/main-c.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'a.html',
      template: './tmp/tmp.html',
      chunks: ['a'] // 載入a對應的打包檔案
    }),
    new HtmlWebpackPlugin({
      filename: 'b.html',
      template: './tmp/tmp.html',
      chunks: ['b']  // // 載入b對應的打包檔案
    }),
    new HtmlWebpackPlugin({
      filename: 'c.html',
      template: './tmp/tmp.html',
      excludeChunks: ['a', 'b'] // 載入非a、b對應的打包檔案
    })
  ]
}
複製程式碼

執行結果

<!-- a.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack learn</title>
  <body>
  <div>hello template</div>
</html>
<script type="text/javascript" src="a.js?82a9a04389852053c167"></script>

<!-- b.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack learn</title>
  <body>
  <div>hello template</div>
</html>
<script type="text/javascript" src="b.js?82a9a04389852053c167"></script>

<!-- c.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack learn</title>
  <body>
  <div>hello template</div>
</html>
<script type="text/javascript" src="c.js?82a9a04389852053c167"></script>
複製程式碼

內聯

除了以連結的形式引入入口檔案,也可以內聯到頁面中。html-webpack-inline-source-plugin外掛專門用來處理入口檔案內聯的

var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: '.(js|css)$' // 所有的js和css檔案內聯引入
    }),
    new HtmlWebpackInlineSourcePlugin()
  ]
}
複製程式碼

babel

babel可以把es最新標準的程式碼轉為es5程式碼,首先需要安裝babel-core核心程式,及babel-loader

npm i babel-loader babel-core -D
複製程式碼

由於ES每年會釋出一個新版本,所以在進行轉換時,需要選擇從哪個標準進行轉換,可供選擇的有'es2015'、'es2016'、'es2017'、'latest'、'env'等多個不同的標準。

babel-preset-env標準是使用最多的,babel-preset-env在沒有任何配置選項的情況下,與 babel-preset-latest(或者babel-preset-es2015,babel-preset-es2016和babel-preset-es2017一起)的行為完全相同

npm i babel-preset-env  -D
複製程式碼
var webpack = require('webpack');
var path = require('path');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  module: {
    rules:[{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['env'],
          cacheDirectory: true
        }
      }
    }]
  }
}
複製程式碼

rules屬性中配置的exclude表示node_modules資料夾不需要babel進行轉換。也可以配置一個include選項,比如include: path.resolve(__dirname, 'src'),表示只有src資料夾中的檔案需要轉換

cacheDirectory選項的預設值是false,設定為true將快取 loader 的執行結果,加快編譯速度

API轉換

babel預設只轉換JavaScript語法,對於新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等是不會轉換的。如果需要轉換API,需要使用babel-polyfill,babel-polyfill是一個全域性墊片

npm i babel-polyfill -D
複製程式碼

入口檔案引入babel-polyfill

// main.js
import 'babel-polyfill';
let set = new Set([1,2,3]);
複製程式碼

babel-polyfill是一個全域性墊片,開發中更常用的是babel-plugin-transform-runtime這個區域性墊片,因為它可以使包的體積更小

npm i babel-plugin-transform-runtime babel-runtime -D
複製程式碼
var webpack = require('webpack');
var path = require('path');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  module: {
    rules:[{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['env'],
          cacheDirectory: true,
          plugins: ['transform-runtime']
        }
      }
    }]
  }
}
複製程式碼

CSS

處理css會用到css-loader和style-loader,css-loader用於讀取並載入css檔案,style-loader將它插入到頁面中

// main.js
require('./assets/styles/cssdemo.css');
複製程式碼
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  module: {
    rules:[{
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({})
  ]
}
複製程式碼

自動字首

由於各大瀏覽器對CSS相容性不同,部分CSS特性需要加上瀏覽器字首才能正常工作,postcss-loader可以幫我們自動完成加字首的工作

npm i postcss-loader autoprefixer postcss-import -D
複製程式碼
module: {
  rules: [{
    test: /\.css$/,
    use: ['style-loader', {
        loader: 'css-loader',
        options: { importLoaders: 1 }
      },
      {
        loader: 'postcss-loader',
        options: {
          plugins: [
            require('postcss-import'), // 解決css中import其他css
            require('autoprefixer')
          ]
        }
      }
    ]
  }]
}
複製程式碼

sass

需要安裝sass-loader及node-sass。

npm i sass-loader node-sass -D
複製程式碼
module: {
  rules: [{
    test: /\.scss$/,
    use: ['style-loader','css-loader',
      {
        loader: 'postcss-loader',
        options: { plugins: [require('autoprefixer')] }
      },
      'sass-loader'
    ]
  }]
}
複製程式碼

分離css

預設情況下,CSS會被打包到入口JS檔案中。如果需要把CSS分離出來,需要使用extract-text-webpack-plugin外掛

npm i extract-text-webpack-plugin@next -D
複製程式碼
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js?[hash]'
  },
  module: {
    rules: [{
      test: /\.scss$/,
      use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: ['css-loader',
          {
            loader: 'postcss-loader',
            options: { plugins: [require('autoprefixer')] }
          },
          'sass-loader'
        ]
      })
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({}),
    new ExtractTextPlugin('main.css')
  ]
}
複製程式碼

圖片資源

webpack處理圖片、字型、音樂、視訊等資源時,需要安裝file-loader

npm i file-loader -D
複製程式碼
// main.js
require('./assets/styles/cssdemo.css');
複製程式碼
/* cssdemo.css */
body {
  background: url('../images/dog.jpg') no-repeat;
}
h1 {
  color: red;
}
複製程式碼
module: {
  rules: [{
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    }, {
      test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
      loader: 'file-loader'
    },
    {
      test: /\.(png|jpe?g|gif|svg)(\?\S*)?$/,
      loader: 'file-loader',
      query: {
        name: '[name].[ext]?[hash]'
      }
    }
  ]
}
複製程式碼

如果是在html模板中,通過img標籤引入圖片,需要使用${require('')}將相對路徑包裹一次

<img src="${require('../src/images/dog.jpg')}" alt="">
複製程式碼

第三方庫

比如說我們通過npm安裝了jQuery,只需要通過provide-plugin外掛就可以自動載入模組,而不必到處 import 或 require

npm i provide-plugin -D
複製程式碼
var ProvidePlugin = require('provide-plugin');

plugins: [
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery'
  })
]
複製程式碼

在專案中使用

console.log($('#box'), jQuery('#box'))
複製程式碼

如果是把jQuery儲存到了本地,可以通過設定別名來引用

resolve:{
  alias:{
    jQuery$: path.resolve(__dirname,'src/libs/jquery.min.js')
  }
}
複製程式碼

實用配置

最後基於上面的所有介紹,把所有的配置綜合到一起。由於程式碼比較多,就不再這裡展開了,我把示例程式碼放到了GitHub

webpack-demo

Blog

參考文章

Babel 入門教程

webpack實用配置

babel-preset-env使用方法

相關文章