1. 程式人生 > >webpack:從入門到真實專案配置(二)

webpack:從入門到真實專案配置(二)

如何在專案中使用 webpack

專案中已經配置了很簡單的 babel 和 webpack,直接執行 npm run start 即可

這時候你會發現這個 bundle.js 居然有這麼大,這肯定是不能接受的,所以接下來章節的主要目的就是將單個檔案拆分

為多個檔案,優化專案。

分離程式碼

先讓我們考慮下快取機制。對於程式碼中依賴的庫很少會去主動升級版本,但是我們自己的程式碼卻每時每刻都在變更,所

以我們可以考慮將依賴的庫和自己的程式碼分割開來,這樣使用者在下一次使用應用時就可以儘量避免重複下載沒有變更的

程式碼,那麼既然要將依賴程式碼提取出來,我們需要變更下入口和出口的部分程式碼。

// 這是 packet.json 中 dependencies 下的
const VENOR = ["faker", "lodash", "react", "react-dom", "react-input-range", "react-redux", "redux", "redux-form", "redux-thunk" ] module.exports = { // 之前我們都是使用了單檔案入口 // entry 同時也支援多檔案入口,現在我們有兩個入口 // 一個是我們自己的程式碼,一個是依賴庫的程式碼 entry: { // bundle 和 vendor 都是自己隨便取名的,會對映到 [name] 中 bundle: './src/index.js'
, vendor: VENOR }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js' }, // ... }

現在我們 build 一下,看看是否有驚喜出現


真的有驚喜。。為什麼 bundle 檔案大小壓根沒變。這是因為 bundle 中也引入了依賴庫的程式碼,剛才的步驟並沒有抽取 

bundle 中引入的程式碼,接下來讓我們學習如何將共同的程式碼抽取出來。

抽取共同程式碼

在這小節我們使用 webpack 自帶的外掛 CommonsChunkPlugin

module
.exports = { //... output: { path: path.join(__dirname, 'dist'), // 既然我們希望快取生效,就應該每次在更改程式碼以後修改檔名 // [chunkhash]會自動根據檔案是否更改而更換雜湊 filename: '[name].[chunkhash].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ // vendor 的意義和之前相同 // manifest檔案是將每次打包都會更改的東西單獨提取出來,保證沒有更改的程式碼無需重新打包,
這樣可以加快打包速度 names: ['vendor', 'manifest'], // 配合 manifest 檔案使用 minChunks: Infinity }) ] };

當我們重新 build 以後,會發現 bundle 檔案很明顯的減小了體積

但是我們使用雜湊來保證快取的同時會發現每次 build 都會生成不一樣的檔案,這時候我們引入另一個外掛來幫助我們

刪除不需要的檔案。

npm install --save-dev clean-webpack-plugin

然後修改配置檔案

module.exports = {
//...
  plugins: [
  // 只刪除 dist 資料夾下的 bundle 和 manifest 檔案
    new CleanWebpackPlugin(['dist/bundle.*.js','dist/manifest.*.js'], {
    // 列印 log
      verbose: true,
      // 刪除檔案
      dry: false
    }),
  ]
};

然後 build 的時候會發現以上檔案被刪除了。

因為我們現在將檔案已經打包成三個 JS 了,以後也許會更多,每次新增 JS 檔案我們都需要手動在 HTML 中新增標籤,

現在我們可以通過一個外掛來自動完成這個功能。

npm install html-webpack-plugin --save-dev

然後修改配置檔案

module.exports = {
//...
  plugins: [
  // 我們這裡將之前的 HTML 檔案當做模板
  // 注意在之前 HTML 檔案中請務必刪除之前引入的 JS 檔案
    new HtmlWebpackPlugin({
      template: 'index.html'
    })
  ]
};

執行 build 操作會發現同時生成了 HTML 檔案,並且已經自動引入了 JS 檔案

按需載入程式碼

在這一小節我們將學習如何按需載入程式碼,在這之前的 vendor 入口我發現忘記加入 router 這個庫了,大家可以加入這

個庫並且重新 build 下,會發現 bundle 只有不到 300KB 了。

現在我們的 bundle 檔案包含了我們全部的自己程式碼。但是當用戶訪問我們的首頁時,其實我們根本無需讓使用者載入除了

首頁以外的程式碼,這個優化我們可以通過路由的非同步載入來完成。

現在修改 src/router.js

// 注意在最新版的 V4路由版本中,更改了按需載入的方式,如果安裝了 V4版,可以自行前往官網學習
import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';

import Home from './components/Home';
import ArtistMain from './components/artists/ArtistMain';

const rootRoute = {
  component: Home,
  path: '/',
  indexRoute: { component: ArtistMain },
  childRoutes: [
    {
      path: 'artists/new',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistCreate')
          .then(module => cb(null, module.default))
      }
    },
    {
      path: 'artists/:id/edit',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistEdit')
          .then(module => cb(null, module.default))
      }
    },
    {
      path: 'artists/:id',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistDetail')
          .then(module => cb(null, module.default))
      }
    }
  ]
}

const Routes = () => {
  return (
    <Router history={hashHistory} routes={rootRoute} />
  );
};

export default Routes;

然後執行 build 命令,可以發現我們的 bundle 檔案又瘦身了,並且新增了幾個檔案

將 HTML 檔案在瀏覽器中開啟,當點選路由跳轉時,可以在開發者工具中的 Network 一欄中看到載入了一個 JS 檔案。

首頁

點選右上角 Random Artist 以後

自動重新整理

每次更新程式碼都需要執行依次 build,並且還要等上一會很麻煩,這一小節介紹如何使用自動重新整理的功能。

首先安裝外掛

npm i --save-dev webpack-dev-server

然後修改 packet.json 檔案

"scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server --open"
  },

現在直接執行 npm run dev 可以發現瀏覽器自動打開了一個空的頁面,並且在命令列中也多了新的輸出

等待編譯完成以後,修改 JS 或者 CSS 檔案,可以發現 webpack 自動幫我們完成了編譯,並且只更新了需要更新的程式碼

但是每次重新重新整理頁面對於 debug 來說很不友好,這時候就需要用到模組熱替換了。但是因為專案中使用了 React,

並且 Vue 或者其他框架都有自己的一套 hot-loader,所以這裡就略過了,有興趣的可以自己學習下。

生成生產環境程式碼

現在我們可以將之前所學和一些新加的外掛整合在一起,build 生產環境程式碼。

npm i --save-dev url-loader optimize-css-assets-webpack-plugin file-loader extract-text-webpack-plugin

修改 webpack 配置

var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanWebpackPlugin = require('clean-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

const VENOR = ["faker",
  "lodash",
  "react",
  "react-dom",
  "react-input-range",
  "react-redux",
  "redux",
  "redux-form",
  "redux-thunk",
  "react-router"
]

module.exports = {
  entry: {
    bundle: './src/index.js',
    vendor: VENOR
  },
  // 如果想修改 webpack-dev-server 配置,在這個物件裡面修改
  devServer: {
    port: 8081
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].[chunkhash].js'
  },
  module: {
    rules: [{
        test: /\.js$/,
        use: 'babel-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [{
            loader: 'url-loader',
            options: {
                limit: 10000,
                name: 'images/[name].[hash:7].[ext]'
            }
        }]
    },
    {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: [{
            // 這邊其實還可以使用 postcss 先處理下 CSS 程式碼
                loader: 'css-loader'
            }]
        })
    },
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['vendor', 'manifest'],
      minChunks: Infinity
    }),
    new CleanWebpackPlugin(['dist/*.js'], {
      verbose: true,
      dry: false
    }),
    new HtmlWebpackPlugin({
      template: 'index.html'
    }),
    // 生成全域性變數
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("process.env.NODE_ENV")
    }),
    // 分離 CSS 程式碼
    new ExtractTextPlugin("css/[name].[contenthash].css"),
    // 壓縮提取出的 CSS,並解決ExtractTextPlugin分離出的 JS 重複問題
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    // 壓縮 JS 程式碼
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

修改 packet.json 檔案

"scripts": {
    "build": "NODE_ENV=production webpack -p",
    "dev": "webpack-dev-server --open"
  }

執行 npm run build

可以看到我們在經歷了這麼多步以後,將 bundle 縮小到了只有 27.1KB,像 vendor 這種常用的庫我們一般可以使用 

CDN 的方式外鏈進來。

補充

webpack 配置上有些實用的小點在上文沒有提到,統一在這裡提一下。

module.exports = {
  resolve: {
  // 副檔名,寫明以後就不需要每個檔案寫字尾
    extensions: ['.js', '.css', '.json'],
 // 路徑別名,比如這裡可以使用 css 指向 static/css 路徑
    alias: {
      '@': resolve('src'),
      'css': resolve('static/css')
    }
  },
  // 生成 source-map,用於打斷點,這裡有好幾個選項
  devtool: '#cheap-module-eval-source-map',
}