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',
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章